Merge lp:~phablet-team/media-hub/oxide-support into lp:media-hub

Proposed by Justin McPherson
Status: Superseded
Proposed branch: lp:~phablet-team/media-hub/oxide-support
Merge into: lp:media-hub
Diff against target: 7164 lines (+6428/-17)
29 files modified
CMakeLists.txt (+1/-0)
debian/control (+1/-0)
include/core/media/player.h (+10/-0)
include/core/media/service.h (+2/-0)
src/core/media/CMakeLists.txt (+3/-1)
src/core/media/codec.h (+40/-0)
src/core/media/engine.h (+4/-0)
src/core/media/gstreamer/engine.cpp (+29/-0)
src/core/media/gstreamer/engine.h (+4/-0)
src/core/media/gstreamer/playbin.h (+61/-3)
src/core/media/mpris/player.h (+6/-0)
src/core/media/mpris/service.h (+26/-0)
src/core/media/player_implementation.cpp (+12/-0)
src/core/media/player_implementation.h (+1/-0)
src/core/media/player_skeleton.cpp (+33/-0)
src/core/media/player_skeleton.h (+2/-0)
src/core/media/player_stub.cpp (+19/-0)
src/core/media/player_stub.h (+3/-0)
src/core/media/service_implementation.cpp (+305/-3)
src/core/media/service_implementation.h (+2/-1)
src/core/media/service_skeleton.cpp (+144/-5)
src/core/media/service_stub.cpp (+30/-0)
src/core/media/service_stub.h (+2/-0)
tests/acceptance-tests/service.cpp (+78/-0)
tests/unit-tests/CMakeLists.txt (+9/-0)
tests/unit-tests/mongoose.c (+5253/-0)
tests/unit-tests/mongoose.h (+153/-0)
tests/unit-tests/test-gstreamer-engine.cpp (+68/-4)
tests/unit-tests/web_server.h (+127/-0)
To merge this branch: bzr merge lp:~phablet-team/media-hub/oxide-support
Reviewer Review Type Date Requested Status
PS Jenkins bot continuous-integration Needs Fixing
Jim Hodapp (community) code Approve
Review via email: mp+236994@code.launchpad.net

This proposal has been superseded by a proposal from 2014-11-18.

Commit message

Support fixed and resumable player sessions.

To post a comment you must log in.
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
45. By Justin McPherson <justin@phablet-dev>

Fix unit test for open_resource_for_uri() headers variant.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
46. By Justin McPherson <justin@phablet-dev>

Use local webserver for headers test.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
47. By Justin McPherson <justin@phablet-dev>

Satisy CI.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
48. By Justin McPherson <justin@phablet-dev>

Fix problem with test web serve.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Jim Hodapp (jhodapp) wrote :

Looks good - four inline comments to look at.

review: Needs Fixing (code)
Revision history for this message
Justin McPherson (justinmcp) :
49. By Justin McPherson <justin@phablet-dev>

Result of review.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Jim Hodapp (jhodapp) :
50. By Justin McPherson <justin@phablet-dev>

Merge from trunk.

51. By Justin McPherson <justin@phablet-dev>

- fix problems from merge
- mark fixed sessions as resumable

Revision history for this message
Jim Hodapp (jhodapp) wrote :

Looks good, thanks. Let me know when this gets built in a silo and I'll help you test it well.

review: Approve (code)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
52. By Justin McPherson <justin@phablet-dev>

Merge from trunk.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
53. By Justin McPherson <justin@phablet-dev>

Merge from trunk.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
54. By Jim Hodapp

Merged with prerequisite branch to merging into trunk

55. By Jim Hodapp

Merged with trunk

Unmerged revisions

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'CMakeLists.txt'
2--- CMakeLists.txt 2014-11-03 00:56:55 +0000
3+++ CMakeLists.txt 2014-11-18 20:58:46 +0000
4@@ -26,6 +26,7 @@
5 pkg_check_modules(DBUS_CPP dbus-cpp REQUIRED)
6 pkg_check_modules(PROCESS_CPP process-cpp REQUIRED)
7 pkg_check_modules(PROPERTIES_CPP properties-cpp REQUIRED)
8+pkg_check_modules(PROCESS_CPP process-cpp REQUIRED)
9 pkg_check_modules(GIO gio-2.0 REQUIRED)
10 pkg_check_modules(HYBRIS_MEDIA libmedia REQUIRED)
11
12
13=== modified file 'debian/control'
14--- debian/control 2014-11-11 20:18:36 +0000
15+++ debian/control 2014-11-18 20:58:46 +0000
16@@ -28,6 +28,7 @@
17 gstreamer1.0-libav,
18 libgstreamer1.0-dev,
19 pkg-config,
20+ libpulse-dev,
21 qtbase5-dev,
22 libtelepathy-qt5-dev,
23 Standards-Version: 3.9.5
24
25=== modified file 'include/core/media/player.h'
26--- include/core/media/player.h 2014-10-14 20:05:20 +0000
27+++ include/core/media/player.h 2014-11-18 20:58:46 +0000
28@@ -42,6 +42,7 @@
29 typedef double Volume;
30 typedef uint32_t PlayerKey;
31 typedef void* GLConsumerWrapperHybris;
32+ typedef std::map<std::string, std::string> HeadersType;
33
34 /** Used to set a callback function to be called when a frame is ready to be rendered **/
35 typedef void (*FrameAvailableCbHybris)(GLConsumerWrapperHybris wrapper, void *context);
36@@ -94,6 +95,12 @@
37 rotate270
38 };
39
40+ enum Lifetime
41+ {
42+ normal,
43+ resumable,
44+ };
45+
46 Player(const Player&) = delete;
47 virtual ~Player();
48
49@@ -104,6 +111,7 @@
50 virtual PlayerKey key() const = 0;
51
52 virtual bool open_uri(const Track::UriType& uri) = 0;
53+ virtual bool open_uri(const Track::UriType& uri, const HeadersType&) = 0;
54 virtual void create_video_sink(uint32_t texture_id) = 0;
55 virtual GLConsumerWrapperHybris gl_consumer() const = 0;
56 virtual void next() = 0;
57@@ -136,12 +144,14 @@
58 virtual const core::Property<int64_t>& duration() const = 0;
59 virtual const core::Property<AudioStreamRole>& audio_stream_role() const = 0;
60 virtual const core::Property<Orientation>& orientation() const = 0;
61+ virtual const core::Property<Lifetime>& lifetime() const = 0;
62
63 virtual core::Property<LoopStatus>& loop_status() = 0;
64 virtual core::Property<PlaybackRate>& playback_rate() = 0;
65 virtual core::Property<bool>& is_shuffle() = 0;
66 virtual core::Property<Volume>& volume() = 0;
67 virtual core::Property<AudioStreamRole>& audio_stream_role() = 0;
68+ virtual core::Property<Lifetime>& lifetime() = 0;
69
70 virtual const core::Signal<int64_t>& seeked_to() const = 0;
71 virtual const core::Signal<void>& end_of_stream() const = 0;
72
73=== modified file 'include/core/media/service.h'
74--- include/core/media/service.h 2014-04-23 19:00:55 +0000
75+++ include/core/media/service.h 2014-11-18 20:58:46 +0000
76@@ -43,6 +43,8 @@
77 bool operator==(const Service&) const = delete;
78
79 virtual std::shared_ptr<Player> create_session(const Player::Configuration&) = 0;
80+ virtual std::shared_ptr<Player> create_fixed_session(const std::string& name, const Player::Configuration&) = 0;
81+ virtual std::shared_ptr<Player> resume_session(Player::PlayerKey) = 0;
82 virtual void pause_other_sessions(Player::PlayerKey) = 0;
83
84 protected:
85
86=== modified file 'src/core/media/CMakeLists.txt'
87--- src/core/media/CMakeLists.txt 2014-11-03 00:56:55 +0000
88+++ src/core/media/CMakeLists.txt 2014-11-18 20:58:46 +0000
89@@ -1,5 +1,6 @@
90 pkg_check_modules(PC_GSTREAMER_1_0 REQUIRED gstreamer-1.0)
91-include_directories(${PC_GSTREAMER_1_0_INCLUDE_DIRS} ${HYBRIS_MEDIA_CFLAGS})
92+pkg_check_modules(PC_PULSE_AUDIO REQUIRED libpulse)
93+include_directories(${PC_GSTREAMER_1_0_INCLUDE_DIRS} ${HYBRIS_MEDIA_CFLAGS} ${PC_PULSE_AUDIO_INCLUDE_DIRS})
94
95 # We compile with all symbols visible by default. For the shipping library, we strip
96 # out all symbols that are not in core::ubuntu::media*
97@@ -100,6 +101,7 @@
98 ${PROCESS_CPP_LDFLAGS}
99 ${GIO_LIBRARIES}
100 ${HYBRIS_MEDIA_LIBRARIES}
101+ ${PC_PULSE_AUDIO_LIBRARIES}
102 )
103
104 include_directories(${PROJECT_SOURCE_DIR}/src/ ${HYBRIS_MEDIA_CFLAGS})
105
106=== modified file 'src/core/media/codec.h'
107--- src/core/media/codec.h 2014-10-10 01:24:43 +0000
108+++ src/core/media/codec.h 2014-11-18 20:58:46 +0000
109@@ -230,6 +230,46 @@
110 }
111 };
112
113+namespace helper
114+{
115+template<>
116+struct TypeMapper<core::ubuntu::media::Player::Lifetime>
117+{
118+ constexpr static ArgumentType type_value()
119+ {
120+ return core::dbus::ArgumentType::int16;
121+ }
122+ constexpr static bool is_basic_type()
123+ {
124+ return false;
125+ }
126+ constexpr static bool requires_signature()
127+ {
128+ return false;
129+ }
130+
131+ static std::string signature()
132+ {
133+ static const std::string s = TypeMapper<std::int16_t>::signature();
134+ return s;
135+ }
136+};
137+}
138+
139+template<>
140+struct Codec<core::ubuntu::media::Player::Lifetime>
141+{
142+ static void encode_argument(core::dbus::Message::Writer& out, const core::ubuntu::media::Player::Lifetime& in)
143+ {
144+ out.push_int16(static_cast<std::int16_t>(in));
145+ }
146+
147+ static void decode_argument(core::dbus::Message::Reader& out, core::ubuntu::media::Player::Lifetime& in)
148+ {
149+ in = static_cast<core::ubuntu::media::Player::Lifetime>(out.pop_int16());
150+ }
151+};
152+
153 }
154 }
155
156
157=== modified file 'src/core/media/engine.h'
158--- src/core/media/engine.h 2014-11-03 18:00:48 +0000
159+++ src/core/media/engine.h 2014-11-18 20:58:46 +0000
160@@ -76,6 +76,7 @@
161 virtual const core::Property<State>& state() const = 0;
162
163 virtual bool open_resource_for_uri(const Track::UriType& uri) = 0;
164+ virtual bool open_resource_for_uri(const core::ubuntu::media::Track::UriType& uri, const Player::HeadersType&) = 0;
165 virtual void create_video_sink(uint32_t texture_id) = 0;
166
167 virtual bool play() = 0;
168@@ -97,6 +98,9 @@
169
170 virtual const core::Property<core::ubuntu::media::Player::Orientation>& orientation() const = 0;
171
172+ virtual const core::Property<core::ubuntu::media::Player::Lifetime>& lifetime() const = 0;
173+ virtual core::Property<core::ubuntu::media::Player::Lifetime>& lifetime() = 0;
174+
175 virtual const core::Property<std::tuple<Track::UriType, Track::MetaData>>& track_meta_data() const = 0;
176
177 virtual const core::Signal<void>& about_to_finish_signal() const = 0;
178
179=== modified file 'src/core/media/gstreamer/engine.cpp'
180--- src/core/media/gstreamer/engine.cpp 2014-11-03 18:00:48 +0000
181+++ src/core/media/gstreamer/engine.cpp 2014-11-18 20:58:46 +0000
182@@ -79,6 +79,11 @@
183 orientation.set(o);
184 }
185
186+ void on_lifetime_changed(const media::Player::Lifetime& lifetime)
187+ {
188+ playbin.set_lifetime(lifetime);
189+ }
190+
191 void on_about_to_finish()
192 {
193 state = Engine::State::ready;
194@@ -146,6 +151,12 @@
195 &Private::on_orientation_changed,
196 this,
197 std::placeholders::_1))),
198+ on_lifetime_changed_connection(
199+ lifetime.changed().connect(
200+ std::bind(
201+ &Private::on_lifetime_changed,
202+ this,
203+ std::placeholders::_1))),
204 on_seeked_to_connection(
205 playbin.signals.on_seeked_to.connect(
206 std::bind(
207@@ -184,6 +195,7 @@
208 core::Property<media::Engine::Volume> volume;
209 core::Property<media::Player::AudioStreamRole> audio_role;
210 core::Property<media::Player::Orientation> orientation;
211+ core::Property<media::Player::Lifetime> lifetime;
212 core::Property<bool> is_video_source;
213 core::Property<bool> is_audio_source;
214
215@@ -193,6 +205,7 @@
216 core::ScopedConnection on_volume_changed_connection;
217 core::ScopedConnection on_audio_stream_role_changed_connection;
218 core::ScopedConnection on_orientation_changed_connection;
219+ core::ScopedConnection on_lifetime_changed_connection;
220 core::ScopedConnection on_seeked_to_connection;
221 core::ScopedConnection client_disconnected_connection;
222 core::ScopedConnection on_end_of_stream_connection;
223@@ -233,6 +246,12 @@
224 return true;
225 }
226
227+bool gstreamer::Engine::open_resource_for_uri(const media::Track::UriType& uri, const core::ubuntu::media::Player::HeadersType& headers)
228+{
229+ d->playbin.set_uri(uri, headers);
230+ return true;
231+}
232+
233 void gstreamer::Engine::create_video_sink(uint32_t texture_id)
234 {
235 d->playbin.create_video_sink(texture_id);
236@@ -338,6 +357,11 @@
237 return d->audio_role;
238 }
239
240+const core::Property<core::ubuntu::media::Player::Lifetime>& gstreamer::Engine::lifetime() const
241+{
242+ return d->lifetime;
243+}
244+
245 core::Property<core::ubuntu::media::Player::AudioStreamRole>& gstreamer::Engine::audio_stream_role()
246 {
247 return d->audio_role;
248@@ -348,6 +372,11 @@
249 return d->orientation;
250 }
251
252+core::Property<core::ubuntu::media::Player::Lifetime>& gstreamer::Engine::lifetime()
253+{
254+ return d->lifetime;
255+}
256+
257 const core::Property<std::tuple<media::Track::UriType, media::Track::MetaData>>&
258 gstreamer::Engine::track_meta_data() const
259 {
260
261=== modified file 'src/core/media/gstreamer/engine.h'
262--- src/core/media/gstreamer/engine.h 2014-11-03 18:00:48 +0000
263+++ src/core/media/gstreamer/engine.h 2014-11-18 20:58:46 +0000
264@@ -34,6 +34,7 @@
265 const core::Property<State>& state() const;
266
267 bool open_resource_for_uri(const core::ubuntu::media::Track::UriType& uri);
268+ bool open_resource_for_uri(const core::ubuntu::media::Track::UriType& uri, const core::ubuntu::media::Player::HeadersType& headers);
269 void create_video_sink(uint32_t texture_id);
270
271 bool play();
272@@ -55,6 +56,9 @@
273
274 const core::Property<core::ubuntu::media::Player::Orientation>& orientation() const;
275
276+ const core::Property<core::ubuntu::media::Player::Lifetime>& lifetime() const;
277+ core::Property<core::ubuntu::media::Player::Lifetime>& lifetime();
278+
279 const core::Property<std::tuple<core::ubuntu::media::Track::UriType, core::ubuntu::media::Track::MetaData>>& track_meta_data() const;
280
281 const core::Signal<void>& about_to_finish_signal() const;
282
283=== modified file 'src/core/media/gstreamer/playbin.h'
284--- src/core/media/gstreamer/playbin.h 2014-11-03 18:00:48 +0000
285+++ src/core/media/gstreamer/playbin.h 2014-11-18 20:58:46 +0000
286@@ -70,6 +70,16 @@
287 thiz->signals.about_to_finish();
288 }
289
290+ static void source_setup(GstElement*,
291+ GstElement *source,
292+ gpointer user_data)
293+ {
294+ if (user_data == nullptr)
295+ return;
296+
297+ static_cast<Playbin*>(user_data)->setup_source(source);
298+ }
299+
300 Playbin()
301 : pipeline(gst_element_factory_make("playbin", pipeline_name().c_str())),
302 bus{gst_element_get_bus(pipeline)},
303@@ -83,7 +93,8 @@
304 &Playbin::on_new_message,
305 this,
306 std::placeholders::_1))),
307- is_seeking(false)
308+ is_seeking(false),
309+ player_lifetime(media::Player::Lifetime::normal)
310 {
311 if (!pipeline)
312 throw std::runtime_error("Could not create pipeline for playbin.");
313@@ -98,6 +109,14 @@
314 G_CALLBACK(about_to_finish),
315 this
316 );
317+
318+ g_signal_connect(
319+ pipeline,
320+ "source-setup",
321+ G_CALLBACK(source_setup),
322+ this
323+ );
324+
325 }
326
327 ~Playbin()
328@@ -112,7 +131,12 @@
329 // When the client dies, tear down the current pipeline and get it
330 // in a state that is ready for the next client that connects to the
331 // service
332- reset_pipeline();
333+
334+ // Don't reset the pipeline if we want to resume
335+ if (player_lifetime != media::Player::Lifetime::resumable) {
336+ reset_pipeline();
337+ }
338+
339 // Signal to the Player class that the client side has disconnected
340 signals.client_disconnected();
341 }
342@@ -307,6 +331,11 @@
343 gst_structure_free (props);
344 }
345
346+ void set_lifetime(media::Player::Lifetime lifetime)
347+ {
348+ player_lifetime = lifetime;
349+ }
350+
351 uint64_t position() const
352 {
353 int64_t pos = 0;
354@@ -325,13 +354,40 @@
355 return static_cast<uint64_t>(dur);
356 }
357
358- void set_uri(const std::string& uri)
359+ void set_uri(const std::string& uri,
360+ const core::ubuntu::media::Player::HeadersType& headers = core::ubuntu::media::Player::HeadersType())
361 {
362+ reset_pipeline();
363+
364 g_object_set(pipeline, "uri", uri.c_str(), NULL);
365 if (is_video_file(uri))
366 file_type = MEDIA_FILE_TYPE_VIDEO;
367 else if (is_audio_file(uri))
368 file_type = MEDIA_FILE_TYPE_AUDIO;
369+
370+ request_headers = headers;
371+ }
372+
373+ void setup_source(GstElement *source)
374+ {
375+ if (source == NULL || request_headers.empty())
376+ return;
377+
378+ if (request_headers.find("Cookie") != request_headers.end()) {
379+ if (g_object_class_find_property(G_OBJECT_GET_CLASS(source),
380+ "cookies") != NULL) {
381+ gchar ** cookies = g_strsplit(request_headers["Cookie"].c_str(), ";", 0);
382+ g_object_set(source, "cookies", cookies, NULL);
383+ g_strfreev(cookies);
384+ }
385+ }
386+
387+ if (request_headers.find("User-Agent") != request_headers.end()) {
388+ if (g_object_class_find_property(G_OBJECT_GET_CLASS(source),
389+ "user-agent") != NULL) {
390+ g_object_set(source, "user-agent", request_headers["User-Agent"].c_str(), NULL);
391+ }
392+ }
393 }
394
395 std::string uri() const
396@@ -503,6 +559,8 @@
397 uint32_t video_width;
398 core::Connection on_new_message_connection;
399 bool is_seeking;
400+ core::ubuntu::media::Player::HeadersType request_headers;
401+ media::Player::Lifetime player_lifetime;
402 struct
403 {
404 core::Signal<void> about_to_finish;
405
406=== modified file 'src/core/media/mpris/player.h'
407--- src/core/media/mpris/player.h 2014-10-14 20:05:20 +0000
408+++ src/core/media/mpris/player.h 2014-11-18 20:58:46 +0000
409@@ -116,6 +116,7 @@
410 DBUS_CPP_METHOD_DEF(CreateVideoSink, Player)
411 DBUS_CPP_METHOD_DEF(Key, Player)
412 DBUS_CPP_METHOD_DEF(OpenUri, Player)
413+ DBUS_CPP_METHOD_DEF(OpenUriExtended, Player)
414
415 struct Signals
416 {
417@@ -134,6 +135,7 @@
418 DBUS_CPP_WRITABLE_PROPERTY_DEF(TypedLoopStatus, Player, core::ubuntu::media::Player::LoopStatus)
419 DBUS_CPP_WRITABLE_PROPERTY_DEF(AudioStreamRole, Player, core::ubuntu::media::Player::AudioStreamRole)
420 DBUS_CPP_READABLE_PROPERTY_DEF(Orientation, Player, core::ubuntu::media::Player::Orientation)
421+ DBUS_CPP_WRITABLE_PROPERTY_DEF(Lifetime, Player, core::ubuntu::media::Player::Lifetime)
422 DBUS_CPP_WRITABLE_PROPERTY_DEF(PlaybackRate, Player, double)
423 DBUS_CPP_WRITABLE_PROPERTY_DEF(Rate, Player, double)
424 DBUS_CPP_WRITABLE_PROPERTY_DEF(Shuffle, Player, bool)
425@@ -215,6 +217,7 @@
426 configuration.object->template get_property<Properties::TypedLoopStatus>(),
427 configuration.object->template get_property<Properties::AudioStreamRole>(),
428 configuration.object->template get_property<Properties::Orientation>(),
429+ configuration.object->template get_property<Properties::Lifetime>(),
430 configuration.object->template get_property<Properties::PlaybackRate>(),
431 configuration.object->template get_property<Properties::Shuffle>(),
432 configuration.object->template get_property<Properties::TypedMetaData>(),
433@@ -247,6 +250,7 @@
434 properties.typed_loop_status->set(configuration.defaults.typed_loop_status);
435 properties.audio_stream_role->set(core::ubuntu::media::Player::AudioStreamRole::multimedia);
436 properties.orientation->set(core::ubuntu::media::Player::Orientation::rotate0);
437+ properties.lifetime->set(core::ubuntu::media::Player::Lifetime::normal);
438 properties.playback_rate->set(configuration.defaults.playback_rate);
439 properties.is_shuffle->set(configuration.defaults.shuffle);
440 properties.position->set(configuration.defaults.position);
441@@ -307,6 +311,7 @@
442 dict[Properties::TypedLoopStatus::name()] = dbus::types::Variant::encode(properties.typed_loop_status->get());
443 dict[Properties::AudioStreamRole::name()] = dbus::types::Variant::encode(properties.audio_stream_role->get());
444 dict[Properties::Orientation::name()] = dbus::types::Variant::encode(properties.orientation->get());
445+ dict[Properties::Lifetime::name()] = dbus::types::Variant::encode(properties.lifetime->get());
446 dict[Properties::PlaybackRate::name()] = dbus::types::Variant::encode(properties.playback_rate->get());
447 dict[Properties::Shuffle::name()] = dbus::types::Variant::encode(properties.is_shuffle->get());
448 dict[Properties::Duration::name()] = dbus::types::Variant::encode(properties.duration->get());
449@@ -337,6 +342,7 @@
450 std::shared_ptr<core::dbus::Property<Properties::TypedLoopStatus>> typed_loop_status;
451 std::shared_ptr<core::dbus::Property<Properties::AudioStreamRole>> audio_stream_role;
452 std::shared_ptr<core::dbus::Property<Properties::Orientation>> orientation;
453+ std::shared_ptr<core::dbus::Property<Properties::Lifetime>> lifetime;
454 std::shared_ptr<core::dbus::Property<Properties::PlaybackRate>> playback_rate;
455 std::shared_ptr<core::dbus::Property<Properties::Shuffle>> is_shuffle;
456 std::shared_ptr<core::dbus::Property<Properties::TypedMetaData>> typed_meta_data_for_current_track;
457
458=== modified file 'src/core/media/mpris/service.h'
459--- src/core/media/mpris/service.h 2014-08-25 10:18:32 +0000
460+++ src/core/media/mpris/service.h 2014-11-18 20:58:46 +0000
461@@ -47,9 +47,35 @@
462 return s;
463 }
464 };
465+
466+ struct CreatingFixedSession
467+ {
468+ static const std::string& name()
469+ {
470+ static const std::string s
471+ {
472+ "core.ubuntu.media.Service.Error.CreatingFixedSession"
473+ };
474+ return s;
475+ }
476+ };
477+
478+ struct ResumingSession
479+ {
480+ static const std::string& name()
481+ {
482+ static const std::string s
483+ {
484+ "core.ubuntu.media.Service.Error.ResumingSession"
485+ };
486+ return s;
487+ }
488+ };
489 };
490
491 DBUS_CPP_METHOD_WITH_TIMEOUT_DEF(CreateSession, Service, 1000)
492+ DBUS_CPP_METHOD_WITH_TIMEOUT_DEF(CreateFixedSession, Service, 1000)
493+ DBUS_CPP_METHOD_WITH_TIMEOUT_DEF(ResumeSession, Service, 1000)
494 DBUS_CPP_METHOD_WITH_TIMEOUT_DEF(PauseOtherSessions, Service, 1000)
495 };
496 }
497
498=== modified file 'src/core/media/player_implementation.cpp'
499--- src/core/media/player_implementation.cpp 2014-11-03 18:00:48 +0000
500+++ src/core/media/player_implementation.cpp 2014-11-18 20:58:46 +0000
501@@ -331,6 +331,8 @@
502 audio_stream_role().set(Player::AudioStreamRole::multimedia);
503 d->engine->audio_stream_role().set(Player::AudioStreamRole::multimedia);
504 orientation().set(Player::Orientation::rotate0);
505+ lifetime().set(Player::Lifetime::normal);
506+ d->engine->lifetime().set(Player::Lifetime::normal);
507
508 // Make sure that the Position property gets updated from the Engine
509 // every time the client requests position
510@@ -374,6 +376,11 @@
511 orientation().set(o);
512 });
513
514+ lifetime().changed().connect([this](media::Player::Lifetime lifetime)
515+ {
516+ d->engine->lifetime().set(lifetime);
517+ });
518+
519 d->engine->about_to_finish_signal().connect([this]()
520 {
521 if (d->track_list->has_next())
522@@ -463,6 +470,11 @@
523 return d->engine->open_resource_for_uri(uri);
524 }
525
526+bool media::PlayerImplementation::open_uri(const Track::UriType& uri, const Player::HeadersType& headers)
527+{
528+ return d->engine->open_resource_for_uri(uri, headers);
529+}
530+
531 void media::PlayerImplementation::create_video_sink(uint32_t texture_id)
532 {
533 d->engine->create_video_sink(texture_id);
534
535=== modified file 'src/core/media/player_implementation.h'
536--- src/core/media/player_implementation.h 2014-09-25 04:35:46 +0000
537+++ src/core/media/player_implementation.h 2014-11-18 20:58:46 +0000
538@@ -47,6 +47,7 @@
539 virtual PlayerKey key() const;
540
541 virtual bool open_uri(const Track::UriType& uri);
542+ virtual bool open_uri(const Track::UriType& uri, const Player::HeadersType& headers);
543 virtual void create_video_sink(uint32_t texture_id);
544 virtual GLConsumerWrapperHybris gl_consumer() const;
545 virtual void next();
546
547=== modified file 'src/core/media/player_skeleton.cpp'
548--- src/core/media/player_skeleton.cpp 2014-10-14 20:15:56 +0000
549+++ src/core/media/player_skeleton.cpp 2014-11-18 20:58:46 +0000
550@@ -240,6 +240,23 @@
551 });
552 }
553
554+ void handle_open_uri_extended(const core::dbus::Message::Ptr& in)
555+ {
556+ dbus_stub.get_connection_app_armor_security_async(in->sender(), [this, in](const std::string& profile)
557+ {
558+ Track::UriType uri;
559+ Player::HeadersType headers;
560+
561+ in->reader() >> uri >> headers;
562+
563+ bool have_access = does_client_have_access(profile, uri);
564+ auto reply = dbus::Message::make_method_return(in);
565+ reply->writer() << (have_access ? impl->open_uri(uri, headers) : false);
566+
567+ bus->send(reply);
568+ });
569+ }
570+
571 template<typename Property>
572 void on_property_value_changed(
573 const typename Property::ValueType& value,
574@@ -353,6 +370,11 @@
575 std::bind(&Private::handle_key,
576 d,
577 std::placeholders::_1));
578+
579+ d->object->install_method_handler<mpris::Player::OpenUriExtended>(
580+ std::bind(&Private::handle_open_uri_extended,
581+ d,
582+ std::placeholders::_1));
583 }
584
585 media::PlayerSkeleton::~PlayerSkeleton()
586@@ -370,6 +392,7 @@
587 d->object->uninstall_method_handler<mpris::Player::OpenUri>();
588 d->object->uninstall_method_handler<mpris::Player::CreateVideoSink>();
589 d->object->uninstall_method_handler<mpris::Player::Key>();
590+ d->object->uninstall_method_handler<mpris::Player::OpenUriExtended>();
591 }
592
593 const core::Property<bool>& media::PlayerSkeleton::can_play() const
594@@ -457,6 +480,11 @@
595 return *d->skeleton.properties.orientation;
596 }
597
598+const core::Property<media::Player::Lifetime>& media::PlayerSkeleton::lifetime() const
599+{
600+ return *d->skeleton.properties.lifetime;
601+}
602+
603 const core::Property<media::Player::PlaybackRate>& media::PlayerSkeleton::minimum_playback_rate() const
604 {
605 return *d->skeleton.properties.minimum_playback_rate;
606@@ -507,6 +535,11 @@
607 return *d->skeleton.properties.orientation;
608 }
609
610+core::Property<media::Player::Lifetime>& media::PlayerSkeleton::lifetime()
611+{
612+ return *d->skeleton.properties.lifetime;
613+}
614+
615 core::Property<media::Player::PlaybackStatus>& media::PlayerSkeleton::playback_status()
616 {
617 return *d->skeleton.properties.typed_playback_status;
618
619=== modified file 'src/core/media/player_skeleton.h'
620--- src/core/media/player_skeleton.h 2014-10-14 20:05:20 +0000
621+++ src/core/media/player_skeleton.h 2014-11-18 20:58:46 +0000
622@@ -63,12 +63,14 @@
623 virtual const core::Property<int64_t>& duration() const;
624 virtual const core::Property<AudioStreamRole>& audio_stream_role() const;
625 virtual const core::Property<Orientation>& orientation() const;
626+ virtual const core::Property<Lifetime>& lifetime() const;
627
628 virtual core::Property<LoopStatus>& loop_status();
629 virtual core::Property<PlaybackRate>& playback_rate();
630 virtual core::Property<bool>& is_shuffle();
631 virtual core::Property<Volume>& volume();
632 virtual core::Property<AudioStreamRole>& audio_stream_role();
633+ virtual core::Property<Lifetime>& lifetime();
634
635 virtual const core::Signal<int64_t>& seeked_to() const;
636 virtual const core::Signal<void>& end_of_stream() const;
637
638=== modified file 'src/core/media/player_stub.cpp'
639--- src/core/media/player_stub.cpp 2014-11-05 20:41:03 +0000
640+++ src/core/media/player_stub.cpp 2014-11-18 20:58:46 +0000
641@@ -76,6 +76,7 @@
642 object->get_property<mpris::Player::Properties::Duration>(),
643 object->get_property<mpris::Player::Properties::AudioStreamRole>(),
644 object->get_property<mpris::Player::Properties::Orientation>(),
645+ object->get_property<mpris::Player::Properties::Lifetime>(),
646 object->get_property<mpris::Player::Properties::MinimumRate>(),
647 object->get_property<mpris::Player::Properties::MaximumRate>()
648 },
649@@ -166,6 +167,7 @@
650 std::shared_ptr<core::dbus::Property<mpris::Player::Properties::Duration>> duration;
651 std::shared_ptr<core::dbus::Property<mpris::Player::Properties::AudioStreamRole>> audio_role;
652 std::shared_ptr<core::dbus::Property<mpris::Player::Properties::Orientation>> orientation;
653+ std::shared_ptr<core::dbus::Property<mpris::Player::Properties::Lifetime>> lifetime;
654 std::shared_ptr<core::dbus::Property<mpris::Player::Properties::MinimumRate>> minimum_playback_rate;
655 std::shared_ptr<core::dbus::Property<mpris::Player::Properties::MaximumRate>> maximum_playback_rate;
656 } properties;
657@@ -281,6 +283,13 @@
658 return op.value();
659 }
660
661+bool media::PlayerStub::open_uri(const Track::UriType& uri, const Player::HeadersType& headers)
662+{
663+ auto op = d->object->invoke_method_synchronously<mpris::Player::OpenUriExtended, bool>(uri, headers);
664+
665+ return op.value();
666+}
667+
668 void media::PlayerStub::create_video_sink(uint32_t texture_id)
669 {
670 auto op = d->object->invoke_method_synchronously<mpris::Player::CreateVideoSink, void>(texture_id);
671@@ -439,6 +448,11 @@
672 return *d->properties.orientation;
673 }
674
675+const core::Property<media::Player::Lifetime>& media::PlayerStub::lifetime() const
676+{
677+ return *d->properties.lifetime;
678+}
679+
680 const core::Property<media::Player::PlaybackRate>& media::PlayerStub::minimum_playback_rate() const
681 {
682 return *d->properties.minimum_playback_rate;
683@@ -474,6 +488,11 @@
684 return *d->properties.audio_role;
685 }
686
687+core::Property<media::Player::Lifetime>& media::PlayerStub::lifetime()
688+{
689+ return *d->properties.lifetime;
690+}
691+
692 const core::Signal<int64_t>& media::PlayerStub::seeked_to() const
693 {
694 return d->signals.seeked_to;
695
696=== modified file 'src/core/media/player_stub.h'
697--- src/core/media/player_stub.h 2014-10-22 20:08:22 +0000
698+++ src/core/media/player_stub.h 2014-11-18 20:58:46 +0000
699@@ -47,6 +47,7 @@
700 virtual PlayerKey key() const;
701
702 virtual bool open_uri(const Track::UriType& uri);
703+ virtual bool open_uri(const Track::UriType& uri, const Player::HeadersType& headers);
704 virtual void create_video_sink(uint32_t texture_id);
705 virtual GLConsumerWrapperHybris gl_consumer() const;
706 virtual void next();
707@@ -78,12 +79,14 @@
708 virtual const core::Property<int64_t>& duration() const;
709 virtual const core::Property<AudioStreamRole>& audio_stream_role() const;
710 virtual const core::Property<Orientation>& orientation() const;
711+ virtual const core::Property<Lifetime>& lifetime() const;
712
713 virtual core::Property<LoopStatus>& loop_status();
714 virtual core::Property<PlaybackRate>& playback_rate();
715 virtual core::Property<bool>& is_shuffle();
716 virtual core::Property<Volume>& volume();
717 virtual core::Property<AudioStreamRole>& audio_stream_role();
718+ virtual core::Property<Lifetime>& lifetime();
719
720 virtual const core::Signal<int64_t>& seeked_to() const;
721 virtual const core::Signal<void>& end_of_stream() const;
722
723=== modified file 'src/core/media/service_implementation.cpp'
724--- src/core/media/service_implementation.cpp 2014-11-03 00:56:55 +0000
725+++ src/core/media/service_implementation.cpp 2014-11-18 20:58:46 +0000
726@@ -15,6 +15,9 @@
727 *
728 * Authored by: Thomas Voß <thomas.voss@canonical.com>
729 * Jim Hodapp <jim.hodapp@canonical.com>
730+ * Ricardo Mendoza <ricardo.mendoza@canonical.com>
731+ *
732+ * Note: Some of the PulseAudio code was adapted from telepathy-ofono
733 */
734
735 #include "service_implementation.h"
736@@ -26,11 +29,15 @@
737
738 #include <boost/asio.hpp>
739
740+#include <string>
741 #include <cstdint>
742+#include <cstring>
743 #include <map>
744 #include <memory>
745 #include <thread>
746
747+#include <pulse/pulseaudio.h>
748+
749 #include "util/timeout.h"
750 #include "unity_screen_service.h"
751 #include <hybris/media/media_recorder_layer.h>
752@@ -45,6 +52,11 @@
753 : resume_key(std::numeric_limits<std::uint32_t>::max()),
754 keep_alive(io_service),
755 disp_cookie(0),
756+ pulse_mainloop_api(nullptr),
757+ pulse_context(nullptr),
758+ headphones_connected(false),
759+ a2dp_connected(false),
760+ primary_idx(0),
761 call_monitor(new CallMonitor)
762 {
763 bus = std::shared_ptr<dbus::Bus>(new dbus::Bus(core::dbus::WellKnownBus::session));
764@@ -54,6 +66,21 @@
765 bus->run();
766 }));
767
768+ // We use Pulse to detect when the user has unplugged speakers from the headphone jack
769+ // or disconnected an A2DP bluetooth device
770+ pulse_mainloop = pa_threaded_mainloop_new();
771+ if (pulse_mainloop == nullptr)
772+ std::cerr << "Unable to create pulseaudio mainloop, audio output detection will not function" << std::endl;
773+
774+ if (pa_threaded_mainloop_start(pulse_mainloop) != 0)
775+ {
776+ std::cerr << "Unable to start pulseaudio mainloop, audio output detection will not function" << std::endl;
777+ pa_threaded_mainloop_free(pulse_mainloop);
778+ pulse_mainloop = nullptr;
779+ }
780+ else
781+ create_pulse_context();
782+
783 // Connect the property change signal that will allow media-hub to take appropriate action
784 // when the battery level reaches critical
785 auto stub_service = dbus::Service::use_service(bus, "com.canonical.indicator.power");
786@@ -74,6 +101,15 @@
787
788 ~Private()
789 {
790+ release_pulse_context();
791+
792+ if (pulse_mainloop != nullptr)
793+ {
794+ pa_threaded_mainloop_stop(pulse_mainloop);
795+ pa_threaded_mainloop_free(pulse_mainloop);
796+ pulse_mainloop = nullptr;
797+ }
798+
799 bus->stop();
800
801 if (worker.joinable())
802@@ -116,6 +152,242 @@
803 p->media_recording_started(started);
804 }
805
806+ pa_threaded_mainloop *mainloop()
807+ {
808+ return pulse_mainloop;
809+ }
810+
811+ bool is_port_available(pa_card_port_info **ports, uint32_t n_ports, const char *name)
812+ {
813+ bool ret = false;
814+
815+ if (ports != nullptr && n_ports > 0 && name != nullptr)
816+ {
817+ for (uint32_t i=0; i<n_ports; i++)
818+ {
819+ if (strstr(ports[i]->name, name) != nullptr && ports[i]->available != PA_PORT_AVAILABLE_NO)
820+ {
821+ ret = true;
822+ break;
823+ }
824+ }
825+ }
826+
827+ return ret;
828+ }
829+
830+ void update_wired_output()
831+ {
832+ const pa_operation *o = pa_context_get_card_info_by_index(pulse_context, primary_idx,
833+ [](pa_context *context, const pa_card_info *info, int eol, void *userdata)
834+ {
835+ (void) context;
836+ (void) eol;
837+
838+ if (info == nullptr || userdata == nullptr)
839+ return;
840+
841+ Private *p = reinterpret_cast<Private*>(userdata);
842+ if (p->is_port_available(info->ports, info->n_ports, "output-wired"))
843+ {
844+ if (!p->headphones_connected)
845+ std::cout << "Wired headphones connected" << std::endl;
846+ p->headphones_connected = true;
847+ }
848+ else if (p->headphones_connected == true)
849+ {
850+ std::cout << "Wired headphones disconnected" << std::endl;
851+ p->headphones_connected = false;
852+ p->pause_playback_if_necessary();
853+ }
854+ }, this);
855+ (void) o;
856+ }
857+
858+ void pause_playback_if_necessary()
859+ {
860+ if (headphones_connected)
861+ return;
862+
863+ // No headphones/fallback on primary sink? Pause.
864+ if (std::get<0>(active_sink) == primary_idx)
865+ pause_playback();
866+ }
867+
868+ void set_active_sink(const char *name)
869+ {
870+ const pa_operation *o = pa_context_get_sink_info_by_name(pulse_context, name,
871+ [](pa_context *context, const pa_sink_info *i, int eol, void *userdata)
872+ {
873+ (void) context;
874+
875+ if (eol)
876+ return;
877+
878+ Private *p = reinterpret_cast<Private*>(userdata);
879+ std::tuple<uint32_t, uint32_t, std::string> new_sink(std::make_tuple(i->index, i->card, i->name));
880+
881+ printf("pulsesink: active_sink=('%s',%d,%d) -> ('%s',%d,%d)\n",
882+ std::get<2>(p->active_sink).c_str(), std::get<0>(p->active_sink),
883+ std::get<1>(p->active_sink), i->name, i->index, i->card);
884+
885+ p->active_sink = new_sink;
886+ p->pause_playback_if_necessary();
887+ }, this);
888+
889+ (void) o;
890+ }
891+
892+ void update_active_sink()
893+ {
894+ const pa_operation *o = pa_context_get_server_info(pulse_context,
895+ [](pa_context *context, const pa_server_info *i, void *userdata)
896+ {
897+ (void) context;
898+
899+ Private *p = reinterpret_cast<Private*>(userdata);
900+ if (i->default_sink_name != std::get<2>(p->active_sink))
901+ p->set_active_sink(i->default_sink_name);
902+ p->update_wired_output();
903+ }, this);
904+
905+ (void) o;
906+ }
907+
908+ void create_pulse_context()
909+ {
910+ if (pulse_context != nullptr)
911+ return;
912+
913+ bool keep_going = true, ok = true;
914+
915+ pulse_mainloop_api = pa_threaded_mainloop_get_api(pulse_mainloop);
916+ pa_threaded_mainloop_lock(pulse_mainloop);
917+
918+ pulse_context = pa_context_new(pulse_mainloop_api, "MediaHubPulseContext");
919+ pa_context_set_state_callback(pulse_context,
920+ [](pa_context *context, void *userdata)
921+ {
922+ (void) context;
923+ Private *p = reinterpret_cast<Private*>(userdata);
924+ // Signals the pa_threaded_mainloop_wait below to proceed
925+ pa_threaded_mainloop_signal(p->mainloop(), 0);
926+ }, this);
927+
928+ if (pulse_context == nullptr)
929+ {
930+ std::cerr << "Unable to create new pulseaudio context" << std::endl;
931+ pa_threaded_mainloop_unlock(pulse_mainloop);
932+ return;
933+ }
934+
935+ if (pa_context_connect(pulse_context, nullptr, PA_CONTEXT_NOAUTOSPAWN, nullptr) < 0)
936+ {
937+ std::cerr << "Unable to create a connection to the pulseaudio context" << std::endl;
938+ pa_threaded_mainloop_unlock(pulse_mainloop);
939+ release_pulse_context();
940+ return;
941+ }
942+
943+ pa_threaded_mainloop_wait(pulse_mainloop);
944+
945+ while (keep_going)
946+ {
947+ switch (pa_context_get_state(pulse_context))
948+ {
949+ case PA_CONTEXT_CONNECTING:
950+ case PA_CONTEXT_AUTHORIZING:
951+ case PA_CONTEXT_SETTING_NAME:
952+ break;
953+
954+ case PA_CONTEXT_READY:
955+ std::cout << "Pulseaudio connection established." << std::endl;
956+ keep_going = false;
957+ break;
958+
959+ case PA_CONTEXT_TERMINATED:
960+ std::cerr << "Pulseaudio context terminated." << std::endl;
961+ keep_going = false;
962+ ok = false;
963+ break;
964+
965+ case PA_CONTEXT_FAILED:
966+ default:
967+ std::cerr << "Pulseaudio connection failure: " << pa_strerror(pa_context_errno(pulse_context));
968+ keep_going = false;
969+ ok = false;
970+ }
971+
972+ if (keep_going)
973+ pa_threaded_mainloop_wait(pulse_mainloop);
974+ }
975+
976+ if (ok)
977+ {
978+ pa_context_set_state_callback(pulse_context,
979+ [](pa_context *context, void *userdata)
980+ {
981+ (void) context;
982+ (void) userdata;
983+ }, this);
984+
985+ //FIXME: Get index for "sink.primary", the default onboard card on Touch.
986+ pa_context_get_sink_info_by_name(pulse_context, "sink.primary",
987+ [](pa_context *context, const pa_sink_info *i, int eol, void *userdata)
988+ {
989+ (void) context;
990+
991+ if (eol)
992+ return;
993+
994+ Private *p = reinterpret_cast<Private*>(userdata);
995+ p->primary_idx = i->index;
996+ p->update_wired_output();
997+ }, this);
998+
999+ update_active_sink();
1000+
1001+ pa_context_set_subscribe_callback(pulse_context,
1002+ [](pa_context *context, pa_subscription_event_type_t t, uint32_t idx, void *userdata)
1003+ {
1004+ (void) context;
1005+ (void) idx;
1006+
1007+ if (userdata == nullptr)
1008+ return;
1009+
1010+ Private *p = reinterpret_cast<Private*>(userdata);
1011+ if ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SINK)
1012+ {
1013+ p->update_active_sink();
1014+ }
1015+ }, this);
1016+ pa_context_subscribe(pulse_context, PA_SUBSCRIPTION_MASK_SINK, nullptr, this);
1017+ }
1018+ else
1019+ {
1020+ if (pulse_context != nullptr)
1021+ {
1022+ pa_context_unref(pulse_context);
1023+ pulse_context = nullptr;
1024+ }
1025+ }
1026+
1027+ pa_threaded_mainloop_unlock(pulse_mainloop);
1028+ }
1029+
1030+ void release_pulse_context()
1031+ {
1032+ if (pulse_context != nullptr)
1033+ {
1034+ pa_threaded_mainloop_lock(pulse_mainloop);
1035+ pa_context_disconnect(pulse_context);
1036+ pa_context_unref(pulse_context);
1037+ pa_threaded_mainloop_unlock(pulse_mainloop);
1038+ pulse_context = nullptr;
1039+ }
1040+ }
1041+
1042 // This holds the key of the multimedia role Player instance that was paused
1043 // when the battery level reached 10% or 5%
1044 media::Player::PlayerKey resume_key;
1045@@ -129,14 +401,22 @@
1046 int disp_cookie;
1047 std::shared_ptr<dbus::Object> uscreen_session;
1048 MediaRecorderObserver *observer;
1049+ pa_mainloop_api *pulse_mainloop_api;
1050+ pa_threaded_mainloop *pulse_mainloop;
1051+ pa_context *pulse_context;
1052+ // Gets signaled when both the headphone jack is removed or an A2DP device is
1053+ // disconnected and playback needs pausing
1054+ core::Signal<void> pause_playback;
1055+ bool headphones_connected;
1056+ bool a2dp_connected;
1057+ std::tuple<uint32_t, uint32_t, std::string> active_sink;
1058+ uint32_t primary_idx;
1059 std::unique_ptr<CallMonitor> call_monitor;
1060 std::list<media::Player::PlayerKey> paused_sessions;
1061 };
1062
1063 media::ServiceImplementation::ServiceImplementation() : d(new Private())
1064 {
1065- cout << __PRETTY_FUNCTION__ << endl;
1066-
1067 d->power_level->changed().connect([this](const core::IndicatorPower::PowerLevel::ValueType &level)
1068 {
1069 // When the battery level hits 10% or 5%, pause all multimedia sessions.
1070@@ -153,6 +433,12 @@
1071 resume_multimedia_session();
1072 });
1073
1074+ d->pause_playback.connect([this]()
1075+ {
1076+ std::cout << "Got pause_playback signal, pausing all multimedia sessions" << std::endl;
1077+ pause_all_multimedia_sessions();
1078+ });
1079+
1080 d->call_monitor->on_change([this](CallMonitor::State state) {
1081 switch (state) {
1082 case CallMonitor::OffHook:
1083@@ -185,13 +471,29 @@
1084 // until all dispatches are done
1085 d->io_service.post([this, key]()
1086 {
1087- remove_player_for_key(key);
1088+ if (!has_player_for_key(key))
1089+ return;
1090+
1091+ if (player_for_key(key)->lifetime() == Player::Lifetime::normal)
1092+ remove_player_for_key(key);
1093 });
1094 });
1095
1096 return player;
1097 }
1098
1099+std::shared_ptr<media::Player> media::ServiceImplementation::create_fixed_session(const std::string&, const media::Player::Configuration&)
1100+{
1101+ // no impl
1102+ return std::shared_ptr<media::Player>();
1103+}
1104+
1105+std::shared_ptr<media::Player> media::ServiceImplementation::resume_session(media::Player::PlayerKey)
1106+{
1107+ // no impl
1108+ return std::shared_ptr<media::Player>();
1109+}
1110+
1111 void media::ServiceImplementation::pause_other_sessions(media::Player::PlayerKey key)
1112 {
1113 if (not has_player_for_key(key))
1114
1115=== modified file 'src/core/media/service_implementation.h'
1116--- src/core/media/service_implementation.h 2014-10-31 07:49:33 +0000
1117+++ src/core/media/service_implementation.h 2014-11-18 20:58:46 +0000
1118@@ -37,7 +37,8 @@
1119 ~ServiceImplementation ();
1120
1121 std::shared_ptr<Player> create_session(const Player::Configuration&);
1122-
1123+ std::shared_ptr<Player> create_fixed_session(const std::string& name, const Player::Configuration&);
1124+ std::shared_ptr<Player> resume_session(Player::PlayerKey key);
1125 void pause_other_sessions(Player::PlayerKey key);
1126
1127 private:
1128
1129=== modified file 'src/core/media/service_skeleton.cpp'
1130--- src/core/media/service_skeleton.cpp 2014-10-14 16:21:47 +0000
1131+++ src/core/media/service_skeleton.cpp 2014-11-18 20:58:46 +0000
1132@@ -14,6 +14,7 @@
1133 * along with this program. If not, see <http://www.gnu.org/licenses/>.
1134 *
1135 * Authored by: Thomas Voß <thomas.voss@canonical.com>
1136+ * Jim Hodapp <jim.hodapp@canonical.com>
1137 */
1138
1139 #include "service_skeleton.h"
1140@@ -62,6 +63,16 @@
1141 &Private::handle_create_session,
1142 this,
1143 std::placeholders::_1));
1144+ object->install_method_handler<mpris::Service::CreateFixedSession>(
1145+ std::bind(
1146+ &Private::handle_create_fixed_session,
1147+ this,
1148+ std::placeholders::_1));
1149+ object->install_method_handler<mpris::Service::ResumeSession>(
1150+ std::bind(
1151+ &Private::handle_resume_session,
1152+ this,
1153+ std::placeholders::_1));
1154 object->install_method_handler<mpris::Service::PauseOtherSessions>(
1155 std::bind(
1156 &Private::handle_pause_other_sessions,
1157@@ -69,15 +80,24 @@
1158 std::placeholders::_1));
1159 }
1160
1161- void handle_create_session(const core::dbus::Message::Ptr& msg)
1162+ std::pair<std::string, media::Player::PlayerKey> create_session_info()
1163 {
1164 static unsigned int session_counter = 0;
1165
1166+ unsigned int current_session = session_counter++;
1167+
1168 std::stringstream ss;
1169- ss << "/core/ubuntu/media/Service/sessions/" << session_counter++;
1170-
1171- dbus::types::ObjectPath op{ss.str()};
1172- media::Player::PlayerKey key{session_counter};
1173+ ss << "/core/ubuntu/media/Service/sessions/" << current_session;
1174+
1175+ return std::make_pair(ss.str(), media::Player::PlayerKey(current_session));
1176+ }
1177+
1178+ void handle_create_session(const core::dbus::Message::Ptr& msg)
1179+ {
1180+ auto session_info = create_session_info();
1181+
1182+ dbus::types::ObjectPath op{session_info.first};
1183+ media::Player::PlayerKey key{session_info.second};
1184
1185 dbus_stub.get_connection_app_armor_security_async(msg->sender(), [this, msg, op, key](const std::string& profile)
1186 {
1187@@ -100,6 +120,117 @@
1188 if (!inserted)
1189 throw std::runtime_error("Problem persisting session in session store.");
1190
1191+
1192+ auto reply = dbus::Message::make_method_return(msg);
1193+ reply->writer() << op;
1194+
1195+ impl->access_bus()->send(reply);
1196+ } catch(const std::runtime_error& e)
1197+ {
1198+ auto reply = dbus::Message::make_error(
1199+ msg,
1200+ mpris::Service::Errors::CreatingSession::name(),
1201+ e.what());
1202+ impl->access_bus()->send(reply);
1203+ }
1204+ });
1205+ }
1206+
1207+ void handle_create_fixed_session(const core::dbus::Message::Ptr& msg)
1208+ {
1209+ dbus_stub.get_connection_app_armor_security_async(msg->sender(), [this, msg](const std::string& profile)
1210+ {
1211+ try
1212+ {
1213+ std::string name;
1214+ msg->reader() >> name;
1215+
1216+ if (fixed_session_store.count(name) == 0) {
1217+ // Create new session
1218+ auto session_info = create_session_info();
1219+
1220+ dbus::types::ObjectPath op{session_info.first};
1221+ media::Player::PlayerKey key{session_info.second};
1222+
1223+ media::Player::Configuration config
1224+ {
1225+ profile,
1226+ key,
1227+ impl->access_bus(),
1228+ impl->access_service()->add_object_for_path(op)
1229+ };
1230+
1231+ auto session = impl->create_session(config);
1232+ session->lifetime().set(media::Player::Lifetime::resumable);
1233+
1234+ bool inserted = false;
1235+ std::tie(std::ignore, inserted)
1236+ = session_store.insert(std::make_pair(key, session));
1237+
1238+ if (!inserted)
1239+ throw std::runtime_error("Problem persisting session in session store.");
1240+
1241+ fixed_session_store.insert(std::make_pair(name, key));
1242+
1243+ auto reply = dbus::Message::make_method_return(msg);
1244+ reply->writer() << op;
1245+
1246+ impl->access_bus()->send(reply);
1247+ }
1248+ else {
1249+ // Resume previous session
1250+ auto key = fixed_session_store[name];
1251+ if (session_store.count(key) == 0) {
1252+ auto reply = dbus::Message::make_error(
1253+ msg,
1254+ mpris::Service::Errors::CreatingFixedSession::name(),
1255+ "Unable to locate player session");
1256+ impl->access_bus()->send(reply);
1257+ return;
1258+ }
1259+
1260+ std::stringstream ss;
1261+ ss << "/core/ubuntu/media/Service/sessions/" << key;
1262+ dbus::types::ObjectPath op{ss.str()};
1263+
1264+ auto reply = dbus::Message::make_method_return(msg);
1265+ reply->writer() << op;
1266+
1267+ impl->access_bus()->send(reply);
1268+ }
1269+ } catch(const std::runtime_error& e)
1270+ {
1271+ auto reply = dbus::Message::make_error(
1272+ msg,
1273+ mpris::Service::Errors::CreatingSession::name(),
1274+ e.what());
1275+ impl->access_bus()->send(reply);
1276+ }
1277+ });
1278+ }
1279+
1280+ void handle_resume_session(const core::dbus::Message::Ptr& msg)
1281+ {
1282+ dbus_stub.get_connection_app_armor_security_async(msg->sender(), [this, msg](const std::string&)
1283+ {
1284+ try
1285+ {
1286+ Player::PlayerKey key;
1287+ msg->reader() >> key;
1288+
1289+ if (session_store.count(key) == 0) {
1290+ auto reply = dbus::Message::make_error(
1291+ msg,
1292+ mpris::Service::Errors::ResumingSession::name(),
1293+ "Unable to locate player session");
1294+ impl->access_bus()->send(reply);
1295+ return;
1296+ }
1297+
1298+ std::stringstream ss;
1299+ ss << "/core/ubuntu/media/Service/sessions/" << key;
1300+ dbus::types::ObjectPath op{ss.str()};
1301+
1302 auto reply = dbus::Message::make_method_return(msg);
1303 reply->writer() << op;
1304
1305@@ -133,6 +264,7 @@
1306 org::freedesktop::dbus::DBus::Stub dbus_stub;
1307 // We track all running player instances.
1308 std::map<media::Player::PlayerKey, std::shared_ptr<media::Player>> session_store;
1309+ std::map<std::string, media::Player::PlayerKey> fixed_session_store;
1310 // We expose the entire service as an MPRIS player.
1311 struct Exported
1312 {
1313@@ -444,6 +576,13 @@
1314
1315 d->session_store.erase(key);
1316 d->exported.unset_if_current(player);
1317+ // All non-durable fixed sessions are also removed
1318+ for (auto it: d->fixed_session_store) {
1319+ if (it.second == key) {
1320+ d->fixed_session_store.erase(it.first);
1321+ break;
1322+ }
1323+ }
1324 }
1325
1326 void media::ServiceSkeleton::run()
1327
1328=== modified file 'src/core/media/service_stub.cpp'
1329--- src/core/media/service_stub.cpp 2014-10-22 20:08:22 +0000
1330+++ src/core/media/service_stub.cpp 2014-11-18 20:58:46 +0000
1331@@ -70,6 +70,36 @@
1332 });
1333 }
1334
1335+std::shared_ptr<media::Player> media::ServiceStub::create_fixed_session(const std::string& name, const media::Player::Configuration&)
1336+{
1337+ auto op = d->object->invoke_method_synchronously<mpris::Service::CreateFixedSession,
1338+ dbus::types::ObjectPath>(name);
1339+
1340+ if (op.is_error())
1341+ throw std::runtime_error("Problem creating session: " + op.error());
1342+
1343+ return std::shared_ptr<media::Player>(new media::PlayerStub
1344+ {
1345+ shared_from_this(),
1346+ access_service()->object_for_path(op.value())
1347+ });
1348+}
1349+
1350+std::shared_ptr<media::Player> media::ServiceStub::resume_session(media::Player::PlayerKey key)
1351+{
1352+ auto op = d->object->invoke_method_synchronously<mpris::Service::ResumeSession,
1353+ dbus::types::ObjectPath>(key);
1354+
1355+ if (op.is_error())
1356+ throw std::runtime_error("Problem resuming session: " + op.error());
1357+
1358+ return std::shared_ptr<media::Player>(new media::PlayerStub
1359+ {
1360+ shared_from_this(),
1361+ access_service()->object_for_path(op.value())
1362+ });
1363+}
1364+
1365 void media::ServiceStub::pause_other_sessions(media::Player::PlayerKey key)
1366 {
1367 std::cout << __PRETTY_FUNCTION__ << std::endl;
1368
1369=== modified file 'src/core/media/service_stub.h'
1370--- src/core/media/service_stub.h 2014-10-16 15:30:44 +0000
1371+++ src/core/media/service_stub.h 2014-11-18 20:58:46 +0000
1372@@ -40,6 +40,8 @@
1373 ~ServiceStub();
1374
1375 std::shared_ptr<Player> create_session(const Player::Configuration&);
1376+ std::shared_ptr<Player> create_fixed_session(const std::string& name, const Player::Configuration&);
1377+ std::shared_ptr<Player> resume_session(Player::PlayerKey key);
1378 void pause_other_sessions(Player::PlayerKey key);
1379
1380 private:
1381
1382=== modified file 'tests/acceptance-tests/service.cpp'
1383--- tests/acceptance-tests/service.cpp 2014-04-04 14:31:43 +0000
1384+++ tests/acceptance-tests/service.cpp 2014-11-18 20:58:46 +0000
1385@@ -107,6 +107,84 @@
1386 core::testing::fork_and_run(service, client));
1387 }
1388
1389+TEST(MusicService, DISABLED_accessing_and_creating_a_fixed_session_works)
1390+{
1391+ core::testing::CrossProcessSync sync_service_start;
1392+
1393+ auto service = [this, &sync_service_start]()
1394+ {
1395+ SigTermCatcher sc;
1396+
1397+ auto service = std::make_shared<media::ServiceImplementation>();
1398+ std::thread t([&service](){service->run();});
1399+
1400+ sync_service_start.try_signal_ready_for(std::chrono::milliseconds{500});
1401+
1402+ sc.wait_for_signal(); service->stop();
1403+
1404+ if (t.joinable())
1405+ t.join();
1406+
1407+ return ::testing::Test::HasFailure() ? core::posix::exit::Status::failure : core::posix::exit::Status::success;
1408+ };
1409+
1410+ auto client = [this, &sync_service_start]()
1411+ {
1412+ sync_service_start.wait_for_signal_ready_for(std::chrono::milliseconds{500});
1413+
1414+ auto service = media::Service::Client::instance();
1415+ auto session = service->create_fixed_session("com.ubuntu.test-session", media::Player::Client::default_configuration());
1416+
1417+ EXPECT_TRUE(session != nullptr);
1418+
1419+ return ::testing::Test::HasFailure() ? core::posix::exit::Status::failure : core::posix::exit::Status::success;
1420+ };
1421+
1422+ EXPECT_EQ(core::testing::ForkAndRunResult::empty,
1423+ core::testing::fork_and_run(service, client));
1424+}
1425+
1426+TEST(MusicService, DISABLED_resuming_a_session_works)
1427+{
1428+ core::testing::CrossProcessSync sync_service_start;
1429+
1430+ auto service = [this, &sync_service_start]()
1431+ {
1432+ SigTermCatcher sc;
1433+
1434+ auto service = std::make_shared<media::ServiceImplementation>();
1435+ std::thread t([&service](){service->run();});
1436+
1437+ sync_service_start.try_signal_ready_for(std::chrono::milliseconds{500});
1438+
1439+ sc.wait_for_signal(); service->stop();
1440+
1441+ if (t.joinable())
1442+ t.join();
1443+
1444+ return ::testing::Test::HasFailure() ? core::posix::exit::Status::failure : core::posix::exit::Status::success;
1445+ };
1446+
1447+ auto client = [this, &sync_service_start]()
1448+ {
1449+ sync_service_start.wait_for_signal_ready_for(std::chrono::milliseconds{500});
1450+
1451+ auto service = media::Service::Client::instance();
1452+ auto session = service->create_session(media::Player::Client::default_configuration());
1453+
1454+ EXPECT_TRUE(session != nullptr);
1455+
1456+ auto resumed_session = service->resume_session(session->key());
1457+
1458+ EXPECT_TRUE(resumed_session != nullptr);
1459+
1460+ return ::testing::Test::HasFailure() ? core::posix::exit::Status::failure : core::posix::exit::Status::success;
1461+ };
1462+
1463+ EXPECT_EQ(core::testing::ForkAndRunResult::empty,
1464+ core::testing::fork_and_run(service, client));
1465+}
1466+
1467 TEST(MusicService, DISABLED_remotely_querying_track_meta_data_works)
1468 {
1469 const std::string test_file{"/tmp/test.ogg"};
1470
1471=== modified file 'tests/unit-tests/CMakeLists.txt'
1472--- tests/unit-tests/CMakeLists.txt 2014-11-03 00:56:55 +0000
1473+++ tests/unit-tests/CMakeLists.txt 2014-11-18 20:58:46 +0000
1474@@ -4,6 +4,11 @@
1475 ${PC_GSTREAMER_1_0_INCLUDE_DIRS}
1476 )
1477
1478+add_library(mongoose mongoose.c)
1479+set_target_properties(
1480+ mongoose
1481+ PROPERTIES COMPILE_FLAGS "-std=c99")
1482+
1483 add_executable(
1484 test-gstreamer-engine
1485
1486@@ -36,10 +41,14 @@
1487 ${PC_GSTREAMER_1_0_LIBRARIES}
1488 ${PROCESS_CPP_LDFLAGS}
1489 ${GIO_LIBRARIES}
1490+ ${PROCESS_CPP_LIBRARIES}
1491+ ${PC_PULSE_AUDIO_LIBRARIES}
1492
1493 gmock
1494 gmock_main
1495 gtest
1496+
1497+ mongoose
1498 )
1499
1500 add_test(test-gstreamer-engine ${CMAKE_CURRENT_BINARY_DIR}/test-gstreamer-engine)
1501
1502=== added file 'tests/unit-tests/mongoose.c'
1503--- tests/unit-tests/mongoose.c 1970-01-01 00:00:00 +0000
1504+++ tests/unit-tests/mongoose.c 2014-11-18 20:58:46 +0000
1505@@ -0,0 +1,5253 @@
1506+// Copyright (c) 2004-2013 Sergey Lyubka <valenok@gmail.com>
1507+// Copyright (c) 2013-2014 Cesanta Software Limited
1508+// All rights reserved
1509+//
1510+// This library is dual-licensed: you can redistribute it and/or modify
1511+// it under the terms of the GNU General Public License version 2 as
1512+// published by the Free Software Foundation. For the terms of this
1513+// license, see <http://www.gnu.org/licenses/>.
1514+//
1515+// You are free to use this library under the terms of the GNU General
1516+// Public License, but WITHOUT ANY WARRANTY; without even the implied
1517+// warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
1518+// See the GNU General Public License for more details.
1519+//
1520+// Alternatively, you can license this library under a commercial
1521+// license, as set out in <http://cesanta.com/>.
1522+//
1523+// $Date: 2014-09-16 06:47:40 UTC $
1524+
1525+#ifdef NOEMBED_NET_SKELETON
1526+#include "net_skeleton.h"
1527+#else
1528+// net_skeleton start
1529+// Copyright (c) 2014 Cesanta Software Limited
1530+// All rights reserved
1531+//
1532+// This software is dual-licensed: you can redistribute it and/or modify
1533+// it under the terms of the GNU General Public License version 2 as
1534+// published by the Free Software Foundation. For the terms of this
1535+// license, see <http://www.gnu.org/licenses/>.
1536+//
1537+// You are free to use this software under the terms of the GNU General
1538+// Public License, but WITHOUT ANY WARRANTY; without even the implied
1539+// warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
1540+// See the GNU General Public License for more details.
1541+//
1542+// Alternatively, you can license this software under a commercial
1543+// license, as set out in <http://cesanta.com/>.
1544+//
1545+// $Date: 2014-09-28 05:04:41 UTC $
1546+
1547+#ifndef NS_SKELETON_HEADER_INCLUDED
1548+#define NS_SKELETON_HEADER_INCLUDED
1549+
1550+#define NS_SKELETON_VERSION "2.1.0"
1551+
1552+#undef UNICODE // Use ANSI WinAPI functions
1553+#undef _UNICODE // Use multibyte encoding on Windows
1554+#define _MBCS // Use multibyte encoding on Windows
1555+#define _INTEGRAL_MAX_BITS 64 // Enable _stati64() on Windows
1556+#define _CRT_SECURE_NO_WARNINGS // Disable deprecation warning in VS2005+
1557+#undef WIN32_LEAN_AND_MEAN // Let windows.h always include winsock2.h
1558+#define _XOPEN_SOURCE 600 // For flockfile() on Linux
1559+#define __STDC_FORMAT_MACROS // <inttypes.h> wants this for C++
1560+#define __STDC_LIMIT_MACROS // C++ wants that for INT64_MAX
1561+#ifndef _LARGEFILE_SOURCE
1562+#define _LARGEFILE_SOURCE // Enable fseeko() and ftello() functions
1563+#endif
1564+#define _FILE_OFFSET_BITS 64 // Enable 64-bit file offsets
1565+
1566+#ifdef _MSC_VER
1567+#pragma warning (disable : 4127) // FD_SET() emits warning, disable it
1568+#pragma warning (disable : 4204) // missing c99 support
1569+#endif
1570+
1571+#include <sys/types.h>
1572+#include <sys/stat.h>
1573+#include <assert.h>
1574+#include <ctype.h>
1575+#include <errno.h>
1576+#include <fcntl.h>
1577+#include <stdarg.h>
1578+#include <stddef.h>
1579+#include <stdio.h>
1580+#include <stdlib.h>
1581+#include <string.h>
1582+#include <time.h>
1583+#include <signal.h>
1584+
1585+#ifdef _WIN32
1586+#ifdef _MSC_VER
1587+#pragma comment(lib, "ws2_32.lib") // Linking with winsock library
1588+#endif
1589+#include <windows.h>
1590+#include <process.h>
1591+#ifndef EINPROGRESS
1592+#define EINPROGRESS WSAEINPROGRESS
1593+#endif
1594+#ifndef EWOULDBLOCK
1595+#define EWOULDBLOCK WSAEWOULDBLOCK
1596+#endif
1597+#ifndef __func__
1598+#define STRX(x) #x
1599+#define STR(x) STRX(x)
1600+#define __func__ __FILE__ ":" STR(__LINE__)
1601+#endif
1602+#ifndef va_copy
1603+#define va_copy(x,y) x = y
1604+#endif // MINGW #defines va_copy
1605+#define snprintf _snprintf
1606+#define vsnprintf _vsnprintf
1607+#define sleep(x) Sleep((x) * 1000)
1608+#define to64(x) _atoi64(x)
1609+typedef int socklen_t;
1610+typedef unsigned char uint8_t;
1611+typedef unsigned int uint32_t;
1612+typedef unsigned short uint16_t;
1613+typedef unsigned __int64 uint64_t;
1614+typedef __int64 int64_t;
1615+typedef SOCKET sock_t;
1616+typedef struct _stati64 ns_stat_t;
1617+#ifndef S_ISDIR
1618+#define S_ISDIR(x) ((x) & _S_IFDIR)
1619+#endif
1620+#else
1621+#include <errno.h>
1622+#include <fcntl.h>
1623+#include <netdb.h>
1624+#include <pthread.h>
1625+#include <stdarg.h>
1626+#include <unistd.h>
1627+#include <arpa/inet.h> // For inet_pton() when NS_ENABLE_IPV6 is defined
1628+#include <netinet/in.h>
1629+#include <sys/socket.h>
1630+#include <sys/select.h>
1631+#define closesocket(x) close(x)
1632+#define __cdecl
1633+#define INVALID_SOCKET (-1)
1634+#define to64(x) strtoll(x, NULL, 10)
1635+typedef int sock_t;
1636+typedef struct stat ns_stat_t;
1637+#endif
1638+
1639+#ifdef NS_ENABLE_DEBUG
1640+#define DBG(x) do { printf("%-20s ", __func__); printf x; putchar('\n'); \
1641+ fflush(stdout); } while(0)
1642+#else
1643+#define DBG(x)
1644+#endif
1645+
1646+#ifndef ARRAY_SIZE
1647+#define ARRAY_SIZE(array) (sizeof(array) / sizeof(array[0]))
1648+#endif
1649+
1650+#ifdef NS_ENABLE_SSL
1651+#ifdef __APPLE__
1652+#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
1653+#endif
1654+#include <openssl/ssl.h>
1655+#else
1656+typedef void *SSL;
1657+typedef void *SSL_CTX;
1658+#endif
1659+
1660+#ifdef __cplusplus
1661+extern "C" {
1662+#endif // __cplusplus
1663+
1664+union socket_address {
1665+ struct sockaddr sa;
1666+ struct sockaddr_in sin;
1667+#ifdef NS_ENABLE_IPV6
1668+ struct sockaddr_in6 sin6;
1669+#else
1670+ struct sockaddr sin6;
1671+#endif
1672+};
1673+
1674+// Describes chunk of memory
1675+struct ns_str {
1676+ const char *p;
1677+ size_t len;
1678+};
1679+
1680+// IO buffers interface
1681+struct iobuf {
1682+ char *buf;
1683+ size_t len;
1684+ size_t size;
1685+};
1686+
1687+void iobuf_init(struct iobuf *, size_t initial_size);
1688+void iobuf_free(struct iobuf *);
1689+size_t iobuf_append(struct iobuf *, const void *data, size_t data_size);
1690+void iobuf_remove(struct iobuf *, size_t data_size);
1691+void iobuf_resize(struct iobuf *, size_t new_size);
1692+
1693+// Callback function (event handler) prototype, must be defined by user.
1694+// Net skeleton will call event handler, passing events defined above.
1695+struct ns_connection;
1696+typedef void (*ns_callback_t)(struct ns_connection *, int event_num, void *evp);
1697+
1698+// Events. Meaning of event parameter (evp) is given in the comment.
1699+#define NS_POLL 0 // Sent to each connection on each call to ns_mgr_poll()
1700+#define NS_ACCEPT 1 // New connection accept()-ed. union socket_address *addr
1701+#define NS_CONNECT 2 // connect() succeeded or failed. int *success_status
1702+#define NS_RECV 3 // Data has benn received. int *num_bytes
1703+#define NS_SEND 4 // Data has been written to a socket. int *num_bytes
1704+#define NS_CLOSE 5 // Connection is closed. NULL
1705+
1706+
1707+struct ns_mgr {
1708+ struct ns_connection *active_connections;
1709+ const char *hexdump_file; // Debug hexdump file path
1710+ sock_t ctl[2]; // Socketpair for mg_wakeup()
1711+ void *user_data; // User data
1712+};
1713+
1714+
1715+struct ns_connection {
1716+ struct ns_connection *next, *prev; // ns_mgr::active_connections linkage
1717+ struct ns_connection *listener; // Set only for accept()-ed connections
1718+ struct ns_mgr *mgr;
1719+
1720+ sock_t sock; // Socket
1721+ union socket_address sa; // Peer address
1722+ struct iobuf recv_iobuf; // Received data
1723+ struct iobuf send_iobuf; // Data scheduled for sending
1724+ SSL *ssl;
1725+ SSL_CTX *ssl_ctx;
1726+ void *user_data; // User-specific data
1727+ void *proto_data; // Application protocol-specific data
1728+ time_t last_io_time; // Timestamp of the last socket IO
1729+ ns_callback_t callback; // Event handler function
1730+
1731+ unsigned int flags;
1732+#define NSF_FINISHED_SENDING_DATA (1 << 0)
1733+#define NSF_BUFFER_BUT_DONT_SEND (1 << 1)
1734+#define NSF_SSL_HANDSHAKE_DONE (1 << 2)
1735+#define NSF_CONNECTING (1 << 3)
1736+#define NSF_CLOSE_IMMEDIATELY (1 << 4)
1737+#define NSF_WANT_READ (1 << 5)
1738+#define NSF_WANT_WRITE (1 << 6)
1739+#define NSF_LISTENING (1 << 7)
1740+#define NSF_UDP (1 << 8)
1741+
1742+#define NSF_USER_1 (1 << 20)
1743+#define NSF_USER_2 (1 << 21)
1744+#define NSF_USER_3 (1 << 22)
1745+#define NSF_USER_4 (1 << 23)
1746+#define NSF_USER_5 (1 << 24)
1747+#define NSF_USER_6 (1 << 25)
1748+};
1749+
1750+void ns_mgr_init(struct ns_mgr *, void *user_data);
1751+void ns_mgr_free(struct ns_mgr *);
1752+time_t ns_mgr_poll(struct ns_mgr *, int milli);
1753+void ns_broadcast(struct ns_mgr *, ns_callback_t, void *, size_t);
1754+
1755+struct ns_connection *ns_next(struct ns_mgr *, struct ns_connection *);
1756+struct ns_connection *ns_add_sock(struct ns_mgr *, sock_t,
1757+ ns_callback_t, void *);
1758+struct ns_connection *ns_bind(struct ns_mgr *, const char *,
1759+ ns_callback_t, void *);
1760+struct ns_connection *ns_connect(struct ns_mgr *, const char *,
1761+ ns_callback_t, void *);
1762+
1763+int ns_send(struct ns_connection *, const void *buf, int len);
1764+int ns_printf(struct ns_connection *, const char *fmt, ...);
1765+int ns_vprintf(struct ns_connection *, const char *fmt, va_list ap);
1766+
1767+// Utility functions
1768+void *ns_start_thread(void *(*f)(void *), void *p);
1769+int ns_socketpair(sock_t [2]);
1770+int ns_socketpair2(sock_t [2], int sock_type); // SOCK_STREAM or SOCK_DGRAM
1771+void ns_set_close_on_exec(sock_t);
1772+void ns_sock_to_str(sock_t sock, char *buf, size_t len, int flags);
1773+int ns_hexdump(const void *buf, int len, char *dst, int dst_len);
1774+int ns_avprintf(char **buf, size_t size, const char *fmt, va_list ap);
1775+int ns_resolve(const char *domain_name, char *ip_addr_buf, size_t buf_len);
1776+
1777+#ifdef __cplusplus
1778+}
1779+#endif // __cplusplus
1780+
1781+#endif // NS_SKELETON_HEADER_INCLUDED
1782+// Copyright (c) 2014 Cesanta Software Limited
1783+// All rights reserved
1784+//
1785+// This software is dual-licensed: you can redistribute it and/or modify
1786+// it under the terms of the GNU General Public License version 2 as
1787+// published by the Free Software Foundation. For the terms of this
1788+// license, see <http://www.gnu.org/licenses/>.
1789+//
1790+// You are free to use this software under the terms of the GNU General
1791+// Public License, but WITHOUT ANY WARRANTY; without even the implied
1792+// warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
1793+// See the GNU General Public License for more details.
1794+//
1795+// Alternatively, you can license this software under a commercial
1796+// license, as set out in <http://cesanta.com/>.
1797+//
1798+// $Date: 2014-09-28 05:04:41 UTC $
1799+
1800+
1801+#ifndef NS_MALLOC
1802+#define NS_MALLOC malloc
1803+#endif
1804+
1805+#ifndef NS_REALLOC
1806+#define NS_REALLOC realloc
1807+#endif
1808+
1809+#ifndef NS_FREE
1810+#define NS_FREE free
1811+#endif
1812+
1813+#define NS_UDP_RECEIVE_BUFFER_SIZE 2000
1814+#define NS_VPRINTF_BUFFER_SIZE 500
1815+
1816+struct ctl_msg {
1817+ ns_callback_t callback;
1818+ char message[1024 * 8];
1819+};
1820+
1821+void iobuf_resize(struct iobuf *io, size_t new_size) {
1822+ char *p;
1823+ if ((new_size > io->size || (new_size < io->size && new_size >= io->len)) &&
1824+ (p = (char *) NS_REALLOC(io->buf, new_size)) != NULL) {
1825+ io->size = new_size;
1826+ io->buf = p;
1827+ }
1828+}
1829+
1830+void iobuf_init(struct iobuf *iobuf, size_t initial_size) {
1831+ iobuf->len = iobuf->size = 0;
1832+ iobuf->buf = NULL;
1833+ iobuf_resize(iobuf, initial_size);
1834+}
1835+
1836+void iobuf_free(struct iobuf *iobuf) {
1837+ if (iobuf != NULL) {
1838+ if (iobuf->buf != NULL) NS_FREE(iobuf->buf);
1839+ iobuf_init(iobuf, 0);
1840+ }
1841+}
1842+
1843+size_t iobuf_append(struct iobuf *io, const void *buf, size_t len) {
1844+ char *p = NULL;
1845+
1846+ assert(io != NULL);
1847+ assert(io->len <= io->size);
1848+
1849+ if (len <= 0) {
1850+ } else if (io->len + len <= io->size) {
1851+ memcpy(io->buf + io->len, buf, len);
1852+ io->len += len;
1853+ } else if ((p = (char *) NS_REALLOC(io->buf, io->len + len)) != NULL) {
1854+ io->buf = p;
1855+ memcpy(io->buf + io->len, buf, len);
1856+ io->len += len;
1857+ io->size = io->len;
1858+ } else {
1859+ len = 0;
1860+ }
1861+
1862+ return len;
1863+}
1864+
1865+void iobuf_remove(struct iobuf *io, size_t n) {
1866+ if (n > 0 && n <= io->len) {
1867+ memmove(io->buf, io->buf + n, io->len - n);
1868+ io->len -= n;
1869+ }
1870+}
1871+
1872+static size_t ns_out(struct ns_connection *nc, const void *buf, size_t len) {
1873+ if (nc->flags & NSF_UDP) {
1874+ long n = sendto(nc->sock, buf, len, 0, &nc->sa.sa, sizeof(nc->sa.sin));
1875+ DBG(("%p %d send %ld (%d %s)", nc, nc->sock, n, errno, strerror(errno)));
1876+ return n < 0 ? 0 : n;
1877+ } else {
1878+ return iobuf_append(&nc->send_iobuf, buf, len);
1879+ }
1880+}
1881+
1882+#ifndef NS_DISABLE_THREADS
1883+void *ns_start_thread(void *(*f)(void *), void *p) {
1884+#ifdef _WIN32
1885+ return (void *) _beginthread((void (__cdecl *)(void *)) f, 0, p);
1886+#else
1887+ pthread_t thread_id = (pthread_t) 0;
1888+ pthread_attr_t attr;
1889+
1890+ (void) pthread_attr_init(&attr);
1891+ (void) pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
1892+
1893+#if defined(NS_STACK_SIZE) && NS_STACK_SIZE > 1
1894+ (void) pthread_attr_setstacksize(&attr, NS_STACK_SIZE);
1895+#endif
1896+
1897+ pthread_create(&thread_id, &attr, f, p);
1898+ pthread_attr_destroy(&attr);
1899+
1900+ return (void *) thread_id;
1901+#endif
1902+}
1903+#endif // NS_DISABLE_THREADS
1904+
1905+static void ns_add_conn(struct ns_mgr *mgr, struct ns_connection *c) {
1906+ c->next = mgr->active_connections;
1907+ mgr->active_connections = c;
1908+ c->prev = NULL;
1909+ if (c->next != NULL) c->next->prev = c;
1910+}
1911+
1912+static void ns_remove_conn(struct ns_connection *conn) {
1913+ if (conn->prev == NULL) conn->mgr->active_connections = conn->next;
1914+ if (conn->prev) conn->prev->next = conn->next;
1915+ if (conn->next) conn->next->prev = conn->prev;
1916+}
1917+
1918+// Print message to buffer. If buffer is large enough to hold the message,
1919+// return buffer. If buffer is to small, allocate large enough buffer on heap,
1920+// and return allocated buffer.
1921+int ns_avprintf(char **buf, size_t size, const char *fmt, va_list ap) {
1922+ va_list ap_copy;
1923+ int len;
1924+
1925+ va_copy(ap_copy, ap);
1926+ len = vsnprintf(*buf, size, fmt, ap_copy);
1927+ va_end(ap_copy);
1928+
1929+ if (len < 0) {
1930+ // eCos and Windows are not standard-compliant and return -1 when
1931+ // the buffer is too small. Keep allocating larger buffers until we
1932+ // succeed or out of memory.
1933+ *buf = NULL;
1934+ while (len < 0) {
1935+ if (*buf) free(*buf);
1936+ size *= 2;
1937+ if ((*buf = (char *) NS_MALLOC(size)) == NULL) break;
1938+ va_copy(ap_copy, ap);
1939+ len = vsnprintf(*buf, size, fmt, ap_copy);
1940+ va_end(ap_copy);
1941+ }
1942+ } else if (len > (int) size) {
1943+ // Standard-compliant code path. Allocate a buffer that is large enough.
1944+ if ((*buf = (char *) NS_MALLOC(len + 1)) == NULL) {
1945+ len = -1;
1946+ } else {
1947+ va_copy(ap_copy, ap);
1948+ len = vsnprintf(*buf, len + 1, fmt, ap_copy);
1949+ va_end(ap_copy);
1950+ }
1951+ }
1952+
1953+ return len;
1954+}
1955+
1956+int ns_vprintf(struct ns_connection *nc, const char *fmt, va_list ap) {
1957+ char mem[NS_VPRINTF_BUFFER_SIZE], *buf = mem;
1958+ int len;
1959+
1960+ if ((len = ns_avprintf(&buf, sizeof(mem), fmt, ap)) > 0) {
1961+ ns_out(nc, buf, len);
1962+ }
1963+ if (buf != mem && buf != NULL) {
1964+ free(buf);
1965+ }
1966+
1967+ return len;
1968+}
1969+
1970+int ns_printf(struct ns_connection *conn, const char *fmt, ...) {
1971+ int len;
1972+ va_list ap;
1973+ va_start(ap, fmt);
1974+ len = ns_vprintf(conn, fmt, ap);
1975+ va_end(ap);
1976+ return len;
1977+}
1978+
1979+static void hexdump(struct ns_connection *nc, const char *path,
1980+ int num_bytes, int ev) {
1981+ const struct iobuf *io = ev == NS_SEND ? &nc->send_iobuf : &nc->recv_iobuf;
1982+ FILE *fp;
1983+ char *buf, src[60], dst[60];
1984+ int buf_size = num_bytes * 5 + 100;
1985+
1986+ if ((fp = fopen(path, "a")) != NULL) {
1987+ ns_sock_to_str(nc->sock, src, sizeof(src), 3);
1988+ ns_sock_to_str(nc->sock, dst, sizeof(dst), 7);
1989+ fprintf(fp, "%lu %p %s %s %s %d\n", (unsigned long) time(NULL),
1990+ nc->user_data, src,
1991+ ev == NS_RECV ? "<-" : ev == NS_SEND ? "->" :
1992+ ev == NS_ACCEPT ? "<A" : ev == NS_CONNECT ? "C>" : "XX",
1993+ dst, num_bytes);
1994+ if (num_bytes > 0 && (buf = (char *) NS_MALLOC(buf_size)) != NULL) {
1995+ ns_hexdump(io->buf + (ev == NS_SEND ? 0 : io->len) -
1996+ (ev == NS_SEND ? 0 : num_bytes), num_bytes, buf, buf_size);
1997+ fprintf(fp, "%s", buf);
1998+ free(buf);
1999+ }
2000+ fclose(fp);
2001+ }
2002+}
2003+
2004+static void ns_call(struct ns_connection *nc, int ev, void *p) {
2005+ if (nc->mgr->hexdump_file != NULL && ev != NS_POLL) {
2006+ int len = (ev == NS_RECV || ev == NS_SEND) ? * (int *) p : 0;
2007+ hexdump(nc, nc->mgr->hexdump_file, len, ev);
2008+ }
2009+
2010+ nc->callback(nc, ev, p);
2011+}
2012+
2013+static void ns_destroy_conn(struct ns_connection *conn) {
2014+ closesocket(conn->sock);
2015+ iobuf_free(&conn->recv_iobuf);
2016+ iobuf_free(&conn->send_iobuf);
2017+#ifdef NS_ENABLE_SSL
2018+ if (conn->ssl != NULL) {
2019+ SSL_free(conn->ssl);
2020+ }
2021+ if (conn->ssl_ctx != NULL) {
2022+ SSL_CTX_free(conn->ssl_ctx);
2023+ }
2024+#endif
2025+ NS_FREE(conn);
2026+}
2027+
2028+static void ns_close_conn(struct ns_connection *conn) {
2029+ DBG(("%p %d", conn, conn->flags));
2030+ ns_call(conn, NS_CLOSE, NULL);
2031+ ns_remove_conn(conn);
2032+ ns_destroy_conn(conn);
2033+}
2034+
2035+void ns_set_close_on_exec(sock_t sock) {
2036+#ifdef _WIN32
2037+ (void) SetHandleInformation((HANDLE) sock, HANDLE_FLAG_INHERIT, 0);
2038+#else
2039+ fcntl(sock, F_SETFD, FD_CLOEXEC);
2040+#endif
2041+}
2042+
2043+static void ns_set_non_blocking_mode(sock_t sock) {
2044+#ifdef _WIN32
2045+ unsigned long on = 1;
2046+ ioctlsocket(sock, FIONBIO, &on);
2047+#else
2048+ int flags = fcntl(sock, F_GETFL, 0);
2049+ fcntl(sock, F_SETFL, flags | O_NONBLOCK);
2050+#endif
2051+}
2052+
2053+#ifndef NS_DISABLE_SOCKETPAIR
2054+int ns_socketpair2(sock_t sp[2], int sock_type) {
2055+ union socket_address sa;
2056+ sock_t sock;
2057+ socklen_t len = sizeof(sa.sin);
2058+ int ret = 0;
2059+
2060+ sp[0] = sp[1] = INVALID_SOCKET;
2061+
2062+ (void) memset(&sa, 0, sizeof(sa));
2063+ sa.sin.sin_family = AF_INET;
2064+ sa.sin.sin_port = htons(0);
2065+ sa.sin.sin_addr.s_addr = htonl(0x7f000001);
2066+
2067+ if ((sock = socket(AF_INET, sock_type, 0)) != INVALID_SOCKET &&
2068+ !bind(sock, &sa.sa, len) &&
2069+ (sock_type == SOCK_DGRAM || !listen(sock, 1)) &&
2070+ !getsockname(sock, &sa.sa, &len) &&
2071+ (sp[0] = socket(AF_INET, sock_type, 0)) != INVALID_SOCKET &&
2072+ !connect(sp[0], &sa.sa, len) &&
2073+ (sock_type == SOCK_STREAM ||
2074+ (!getsockname(sp[0], &sa.sa, &len) && !connect(sock, &sa.sa, len))) &&
2075+ (sp[1] = (sock_type == SOCK_DGRAM ? sock :
2076+ accept(sock, &sa.sa, &len))) != INVALID_SOCKET) {
2077+ ns_set_close_on_exec(sp[0]);
2078+ ns_set_close_on_exec(sp[1]);
2079+ ret = 1;
2080+ } else {
2081+ if (sp[0] != INVALID_SOCKET) closesocket(sp[0]);
2082+ if (sp[1] != INVALID_SOCKET) closesocket(sp[1]);
2083+ sp[0] = sp[1] = INVALID_SOCKET;
2084+ }
2085+ if (sock_type != SOCK_DGRAM) closesocket(sock);
2086+
2087+ return ret;
2088+}
2089+
2090+int ns_socketpair(sock_t sp[2]) {
2091+ return ns_socketpair2(sp, SOCK_STREAM);
2092+}
2093+#endif // NS_DISABLE_SOCKETPAIR
2094+
2095+// TODO(lsm): use non-blocking resolver
2096+static int ns_resolve2(const char *host, struct in_addr *ina) {
2097+ struct hostent *he;
2098+ if ((he = gethostbyname(host)) == NULL) {
2099+ DBG(("gethostbyname(%s) failed: %s", host, strerror(errno)));
2100+ } else {
2101+ memcpy(ina, he->h_addr_list[0], sizeof(*ina));
2102+ return 1;
2103+ }
2104+ return 0;
2105+}
2106+
2107+// Resolve FDQN "host", store IP address in the "ip".
2108+// Return > 0 (IP address length) on success.
2109+int ns_resolve(const char *host, char *buf, size_t n) {
2110+ struct in_addr ad;
2111+ return ns_resolve2(host, &ad) ? snprintf(buf, n, "%s", inet_ntoa(ad)) : 0;
2112+}
2113+
2114+// Address format: [PROTO://][IP_ADDRESS:]PORT[:CERT][:CA_CERT]
2115+static int ns_parse_address(const char *str, union socket_address *sa,
2116+ int *proto, int *use_ssl, char *cert, char *ca) {
2117+ unsigned int a, b, c, d, port;
2118+ int n = 0, len = 0;
2119+ char host[200];
2120+#ifdef NS_ENABLE_IPV6
2121+ char buf[100];
2122+#endif
2123+
2124+ // MacOS needs that. If we do not zero it, subsequent bind() will fail.
2125+ // Also, all-zeroes in the socket address means binding to all addresses
2126+ // for both IPv4 and IPv6 (INADDR_ANY and IN6ADDR_ANY_INIT).
2127+ memset(sa, 0, sizeof(*sa));
2128+ sa->sin.sin_family = AF_INET;
2129+
2130+ *proto = SOCK_STREAM;
2131+ *use_ssl = 0;
2132+ cert[0] = ca[0] = '\0';
2133+
2134+ if (memcmp(str, "ssl://", 6) == 0) {
2135+ str += 6;
2136+ *use_ssl = 1;
2137+ } else if (memcmp(str, "udp://", 6) == 0) {
2138+ str += 6;
2139+ *proto = SOCK_DGRAM;
2140+ } else if (memcmp(str, "tcp://", 6) == 0) {
2141+ str += 6;
2142+ }
2143+
2144+ if (sscanf(str, "%u.%u.%u.%u:%u%n", &a, &b, &c, &d, &port, &len) == 5) {
2145+ // Bind to a specific IPv4 address, e.g. 192.168.1.5:8080
2146+ sa->sin.sin_addr.s_addr = htonl((a << 24) | (b << 16) | (c << 8) | d);
2147+ sa->sin.sin_port = htons((uint16_t) port);
2148+#ifdef NS_ENABLE_IPV6
2149+ } else if (sscanf(str, "[%99[^]]]:%u%n", buf, &port, &len) == 2 &&
2150+ inet_pton(AF_INET6, buf, &sa->sin6.sin6_addr)) {
2151+ // IPv6 address, e.g. [3ffe:2a00:100:7031::1]:8080
2152+ sa->sin6.sin6_family = AF_INET6;
2153+ sa->sin6.sin6_port = htons((uint16_t) port);
2154+#endif
2155+ } else if (sscanf(str, "%199[^ :]:%u%n", host, &port, &len) == 2) {
2156+ sa->sin.sin_port = htons((uint16_t) port);
2157+ ns_resolve2(host, &sa->sin.sin_addr);
2158+ } else if (sscanf(str, "%u%n", &port, &len) == 1) {
2159+ // If only port is specified, bind to IPv4, INADDR_ANY
2160+ sa->sin.sin_port = htons((uint16_t) port);
2161+ }
2162+
2163+ if (*use_ssl && (sscanf(str + len, ":%99[^:]:%99[^:]%n", cert, ca, &n) == 2 ||
2164+ sscanf(str + len, ":%99[^:]%n", cert, &n) == 1)) {
2165+ len += n;
2166+ }
2167+
2168+ return port < 0xffff && str[len] == '\0' ? len : 0;
2169+}
2170+
2171+// 'sa' must be an initialized address to bind to
2172+static sock_t ns_open_listening_socket(union socket_address *sa, int proto) {
2173+ socklen_t sa_len = (sa->sa.sa_family == AF_INET) ?
2174+ sizeof(sa->sin) : sizeof(sa->sin6);
2175+ sock_t sock = INVALID_SOCKET;
2176+#ifndef _WIN32
2177+ int on = 1;
2178+#endif
2179+
2180+ if ((sock = socket(sa->sa.sa_family, proto, 0)) != INVALID_SOCKET &&
2181+#ifndef _WIN32
2182+ // SO_RESUSEADDR is not enabled on Windows because the semantics of
2183+ // SO_REUSEADDR on UNIX and Windows is different. On Windows,
2184+ // SO_REUSEADDR allows to bind a socket to a port without error even if
2185+ // the port is already open by another program. This is not the behavior
2186+ // SO_REUSEADDR was designed for, and leads to hard-to-track failure
2187+ // scenarios. Therefore, SO_REUSEADDR was disabled on Windows.
2188+ !setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (void *) &on, sizeof(on)) &&
2189+#endif
2190+ !bind(sock, &sa->sa, sa_len) &&
2191+ (proto == SOCK_DGRAM || listen(sock, SOMAXCONN) == 0)) {
2192+ ns_set_non_blocking_mode(sock);
2193+ // In case port was set to 0, get the real port number
2194+ (void) getsockname(sock, &sa->sa, &sa_len);
2195+ } else if (sock != INVALID_SOCKET) {
2196+ closesocket(sock);
2197+ sock = INVALID_SOCKET;
2198+ }
2199+
2200+ return sock;
2201+}
2202+
2203+#ifdef NS_ENABLE_SSL
2204+// Certificate generation script is at
2205+// https://github.com/cesanta/net_skeleton/blob/master/scripts/gen_certs.sh
2206+
2207+static int ns_use_ca_cert(SSL_CTX *ctx, const char *cert) {
2208+ if (ctx == NULL) {
2209+ return -1;
2210+ } else if (cert == NULL || cert[0] == '\0') {
2211+ return 0;
2212+ }
2213+ SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, 0);
2214+ return SSL_CTX_load_verify_locations(ctx, cert, NULL) == 1 ? 0 : -2;
2215+}
2216+
2217+static int ns_use_cert(SSL_CTX *ctx, const char *pem_file) {
2218+ if (ctx == NULL) {
2219+ return -1;
2220+ } else if (pem_file == NULL || pem_file[0] == '\0') {
2221+ return 0;
2222+ } else if (SSL_CTX_use_certificate_file(ctx, pem_file, 1) == 0 ||
2223+ SSL_CTX_use_PrivateKey_file(ctx, pem_file, 1) == 0) {
2224+ return -2;
2225+ } else {
2226+ SSL_CTX_set_mode(ctx, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
2227+ SSL_CTX_use_certificate_chain_file(ctx, pem_file);
2228+ return 0;
2229+ }
2230+}
2231+#endif // NS_ENABLE_SSL
2232+
2233+struct ns_connection *ns_bind(struct ns_mgr *srv, const char *str,
2234+ ns_callback_t callback, void *user_data) {
2235+ union socket_address sa;
2236+ struct ns_connection *nc = NULL;
2237+ int use_ssl, proto;
2238+ char cert[100], ca_cert[100];
2239+ sock_t sock;
2240+
2241+ ns_parse_address(str, &sa, &proto, &use_ssl, cert, ca_cert);
2242+ if (use_ssl && cert[0] == '\0') return NULL;
2243+
2244+ if ((sock = ns_open_listening_socket(&sa, proto)) == INVALID_SOCKET) {
2245+ } else if ((nc = ns_add_sock(srv, sock, callback, NULL)) == NULL) {
2246+ closesocket(sock);
2247+ } else {
2248+ nc->sa = sa;
2249+ nc->flags |= NSF_LISTENING;
2250+ nc->user_data = user_data;
2251+ nc->callback = callback;
2252+
2253+ if (proto == SOCK_DGRAM) {
2254+ nc->flags |= NSF_UDP;
2255+ }
2256+
2257+#ifdef NS_ENABLE_SSL
2258+ if (use_ssl) {
2259+ nc->ssl_ctx = SSL_CTX_new(SSLv23_server_method());
2260+ if (ns_use_cert(nc->ssl_ctx, cert) != 0 ||
2261+ ns_use_ca_cert(nc->ssl_ctx, ca_cert) != 0) {
2262+ ns_close_conn(nc);
2263+ nc = NULL;
2264+ }
2265+ }
2266+#endif
2267+
2268+ DBG(("%p sock %d/%d ssl %p %p", nc, sock, proto, nc->ssl_ctx, nc->ssl));
2269+ }
2270+
2271+ return nc;
2272+}
2273+
2274+static struct ns_connection *accept_conn(struct ns_connection *ls) {
2275+ struct ns_connection *c = NULL;
2276+ union socket_address sa;
2277+ socklen_t len = sizeof(sa);
2278+ sock_t sock = INVALID_SOCKET;
2279+
2280+ // NOTE(lsm): on Windows, sock is always > FD_SETSIZE
2281+ if ((sock = accept(ls->sock, &sa.sa, &len)) == INVALID_SOCKET) {
2282+ } else if ((c = ns_add_sock(ls->mgr, sock, ls->callback,
2283+ ls->user_data)) == NULL) {
2284+ closesocket(sock);
2285+#ifdef NS_ENABLE_SSL
2286+ } else if (ls->ssl_ctx != NULL &&
2287+ ((c->ssl = SSL_new(ls->ssl_ctx)) == NULL ||
2288+ SSL_set_fd(c->ssl, sock) != 1)) {
2289+ DBG(("SSL error"));
2290+ ns_close_conn(c);
2291+ c = NULL;
2292+#endif
2293+ } else {
2294+ c->listener = ls;
2295+ c->proto_data = ls->proto_data;
2296+ ns_call(c, NS_ACCEPT, &sa);
2297+ DBG(("%p %d %p %p", c, c->sock, c->ssl_ctx, c->ssl));
2298+ }
2299+
2300+ return c;
2301+}
2302+
2303+static int ns_is_error(int n) {
2304+ return n == 0 ||
2305+ (n < 0 && errno != EINTR && errno != EINPROGRESS &&
2306+ errno != EAGAIN && errno != EWOULDBLOCK
2307+#ifdef _WIN32
2308+ && WSAGetLastError() != WSAEINTR && WSAGetLastError() != WSAEWOULDBLOCK
2309+#endif
2310+ );
2311+}
2312+
2313+void ns_sock_to_str(sock_t sock, char *buf, size_t len, int flags) {
2314+ union socket_address sa;
2315+ socklen_t slen = sizeof(sa);
2316+
2317+ if (buf != NULL && len > 0) {
2318+ buf[0] = '\0';
2319+ memset(&sa, 0, sizeof(sa));
2320+ if (flags & 4) {
2321+ getpeername(sock, &sa.sa, &slen);
2322+ } else {
2323+ getsockname(sock, &sa.sa, &slen);
2324+ }
2325+ if (flags & 1) {
2326+#if defined(NS_ENABLE_IPV6)
2327+ inet_ntop(sa.sa.sa_family, sa.sa.sa_family == AF_INET ?
2328+ (void *) &sa.sin.sin_addr :
2329+ (void *) &sa.sin6.sin6_addr, buf, len);
2330+#elif defined(_WIN32)
2331+ // Only Windoze Vista (and newer) have inet_ntop()
2332+ strncpy(buf, inet_ntoa(sa.sin.sin_addr), len);
2333+#else
2334+ inet_ntop(sa.sa.sa_family, (void *) &sa.sin.sin_addr, buf,(socklen_t)len);
2335+#endif
2336+ }
2337+ if (flags & 2) {
2338+ snprintf(buf + strlen(buf), len - (strlen(buf) + 1), "%s%d",
2339+ flags & 1 ? ":" : "", (int) ntohs(sa.sin.sin_port));
2340+ }
2341+ }
2342+}
2343+
2344+int ns_hexdump(const void *buf, int len, char *dst, int dst_len) {
2345+ const unsigned char *p = (const unsigned char *) buf;
2346+ char ascii[17] = "";
2347+ int i, idx, n = 0;
2348+
2349+ for (i = 0; i < len; i++) {
2350+ idx = i % 16;
2351+ if (idx == 0) {
2352+ if (i > 0) n += snprintf(dst + n, dst_len - n, " %s\n", ascii);
2353+ n += snprintf(dst + n, dst_len - n, "%04x ", i);
2354+ }
2355+ n += snprintf(dst + n, dst_len - n, " %02x", p[i]);
2356+ ascii[idx] = p[i] < 0x20 || p[i] > 0x7e ? '.' : p[i];
2357+ ascii[idx + 1] = '\0';
2358+ }
2359+
2360+ while (i++ % 16) n += snprintf(dst + n, dst_len - n, "%s", " ");
2361+ n += snprintf(dst + n, dst_len - n, " %s\n\n", ascii);
2362+
2363+ return n;
2364+}
2365+
2366+#ifdef NS_ENABLE_SSL
2367+static int ns_ssl_err(struct ns_connection *conn, int res) {
2368+ int ssl_err = SSL_get_error(conn->ssl, res);
2369+ if (ssl_err == SSL_ERROR_WANT_READ) conn->flags |= NSF_WANT_READ;
2370+ if (ssl_err == SSL_ERROR_WANT_WRITE) conn->flags |= NSF_WANT_WRITE;
2371+ return ssl_err;
2372+}
2373+#endif
2374+
2375+static void ns_read_from_socket(struct ns_connection *conn) {
2376+ char buf[2048];
2377+ int n = 0;
2378+
2379+ if (conn->flags & NSF_CONNECTING) {
2380+ int ok = 1, ret;
2381+ socklen_t len = sizeof(ok);
2382+
2383+ ret = getsockopt(conn->sock, SOL_SOCKET, SO_ERROR, (char *) &ok, &len);
2384+ (void) ret;
2385+#ifdef NS_ENABLE_SSL
2386+ if (ret == 0 && ok == 0 && conn->ssl != NULL) {
2387+ int res = SSL_connect(conn->ssl);
2388+ int ssl_err = ns_ssl_err(conn, res);
2389+ if (res == 1) {
2390+ conn->flags |= NSF_SSL_HANDSHAKE_DONE;
2391+ } else if (ssl_err == SSL_ERROR_WANT_READ ||
2392+ ssl_err == SSL_ERROR_WANT_WRITE) {
2393+ return; // Call us again
2394+ } else {
2395+ ok = 1;
2396+ }
2397+ }
2398+#endif
2399+ conn->flags &= ~NSF_CONNECTING;
2400+ DBG(("%p ok=%d", conn, ok));
2401+ if (ok != 0) {
2402+ conn->flags |= NSF_CLOSE_IMMEDIATELY;
2403+ }
2404+ ns_call(conn, NS_CONNECT, &ok);
2405+ return;
2406+ }
2407+
2408+#ifdef NS_ENABLE_SSL
2409+ if (conn->ssl != NULL) {
2410+ if (conn->flags & NSF_SSL_HANDSHAKE_DONE) {
2411+ // SSL library may have more bytes ready to read then we ask to read.
2412+ // Therefore, read in a loop until we read everything. Without the loop,
2413+ // we skip to the next select() cycle which can just timeout.
2414+ while ((n = SSL_read(conn->ssl, buf, sizeof(buf))) > 0) {
2415+ DBG(("%p %d <- %d bytes (SSL)", conn, conn->flags, n));
2416+ iobuf_append(&conn->recv_iobuf, buf, n);
2417+ ns_call(conn, NS_RECV, &n);
2418+ }
2419+ ns_ssl_err(conn, n);
2420+ } else {
2421+ int res = SSL_accept(conn->ssl);
2422+ int ssl_err = ns_ssl_err(conn, res);
2423+ if (res == 1) {
2424+ conn->flags |= NSF_SSL_HANDSHAKE_DONE;
2425+ } else if (ssl_err == SSL_ERROR_WANT_READ ||
2426+ ssl_err == SSL_ERROR_WANT_WRITE) {
2427+ return; // Call us again
2428+ } else {
2429+ conn->flags |= NSF_CLOSE_IMMEDIATELY;
2430+ }
2431+ return;
2432+ }
2433+ } else
2434+#endif
2435+ {
2436+ while ((n = (int) recv(conn->sock, buf, sizeof(buf), 0)) > 0) {
2437+ DBG(("%p %d <- %d bytes (PLAIN)", conn, conn->flags, n));
2438+ iobuf_append(&conn->recv_iobuf, buf, n);
2439+ ns_call(conn, NS_RECV, &n);
2440+ }
2441+ }
2442+
2443+ if (ns_is_error(n)) {
2444+ conn->flags |= NSF_CLOSE_IMMEDIATELY;
2445+ }
2446+}
2447+
2448+static void ns_write_to_socket(struct ns_connection *conn) {
2449+ struct iobuf *io = &conn->send_iobuf;
2450+ int n = 0;
2451+
2452+#ifdef NS_ENABLE_SSL
2453+ if (conn->ssl != NULL) {
2454+ n = SSL_write(conn->ssl, io->buf, io->len);
2455+ if (n <= 0) {
2456+ int ssl_err = ns_ssl_err(conn, n);
2457+ if (ssl_err == SSL_ERROR_WANT_READ || ssl_err == SSL_ERROR_WANT_WRITE) {
2458+ return; // Call us again
2459+ } else {
2460+ conn->flags |= NSF_CLOSE_IMMEDIATELY;
2461+ }
2462+ }
2463+ } else
2464+#endif
2465+ { n = (int) send(conn->sock, io->buf, io->len, 0); }
2466+
2467+ DBG(("%p %d -> %d bytes", conn, conn->flags, n));
2468+
2469+ ns_call(conn, NS_SEND, &n);
2470+ if (ns_is_error(n)) {
2471+ conn->flags |= NSF_CLOSE_IMMEDIATELY;
2472+ } else if (n > 0) {
2473+ iobuf_remove(io, n);
2474+ }
2475+}
2476+
2477+int ns_send(struct ns_connection *conn, const void *buf, int len) {
2478+ return (int) ns_out(conn, buf, len);
2479+}
2480+
2481+static void ns_handle_udp(struct ns_connection *ls) {
2482+ struct ns_connection nc;
2483+ char buf[NS_UDP_RECEIVE_BUFFER_SIZE];
2484+ int n;
2485+ socklen_t s_len = sizeof(nc.sa);
2486+
2487+ memset(&nc, 0, sizeof(nc));
2488+ n = recvfrom(ls->sock, buf, sizeof(buf), 0, &nc.sa.sa, &s_len);
2489+ if (n <= 0) {
2490+ DBG(("%p recvfrom: %s", ls, strerror(errno)));
2491+ } else {
2492+ nc.mgr = ls->mgr;
2493+ nc.recv_iobuf.buf = buf;
2494+ nc.recv_iobuf.len = nc.recv_iobuf.size = n;
2495+ nc.sock = ls->sock;
2496+ nc.callback = ls->callback;
2497+ nc.user_data = ls->user_data;
2498+ nc.proto_data = ls->proto_data;
2499+ nc.mgr = ls->mgr;
2500+ nc.listener = ls;
2501+ nc.flags = NSF_UDP;
2502+ DBG(("%p %d bytes received", ls, n));
2503+ ns_call(&nc, NS_RECV, &n);
2504+ }
2505+}
2506+
2507+static void ns_add_to_set(sock_t sock, fd_set *set, sock_t *max_fd) {
2508+ if (sock != INVALID_SOCKET) {
2509+ FD_SET(sock, set);
2510+ if (*max_fd == INVALID_SOCKET || sock > *max_fd) {
2511+ *max_fd = sock;
2512+ }
2513+ }
2514+}
2515+
2516+time_t ns_mgr_poll(struct ns_mgr *mgr, int milli) {
2517+ struct ns_connection *conn, *tmp_conn;
2518+ struct timeval tv;
2519+ fd_set read_set, write_set;
2520+ sock_t max_fd = INVALID_SOCKET;
2521+ time_t current_time = time(NULL);
2522+
2523+ FD_ZERO(&read_set);
2524+ FD_ZERO(&write_set);
2525+ ns_add_to_set(mgr->ctl[1], &read_set, &max_fd);
2526+
2527+ for (conn = mgr->active_connections; conn != NULL; conn = tmp_conn) {
2528+ tmp_conn = conn->next;
2529+ if (!(conn->flags & (NSF_LISTENING | NSF_CONNECTING))) {
2530+ ns_call(conn, NS_POLL, &current_time);
2531+ }
2532+ if (!(conn->flags & NSF_WANT_WRITE)) {
2533+ //DBG(("%p read_set", conn));
2534+ ns_add_to_set(conn->sock, &read_set, &max_fd);
2535+ }
2536+ if (((conn->flags & NSF_CONNECTING) && !(conn->flags & NSF_WANT_READ)) ||
2537+ (conn->send_iobuf.len > 0 && !(conn->flags & NSF_CONNECTING) &&
2538+ !(conn->flags & NSF_BUFFER_BUT_DONT_SEND))) {
2539+ //DBG(("%p write_set", conn));
2540+ ns_add_to_set(conn->sock, &write_set, &max_fd);
2541+ }
2542+ if (conn->flags & NSF_CLOSE_IMMEDIATELY) {
2543+ ns_close_conn(conn);
2544+ }
2545+ }
2546+
2547+ tv.tv_sec = milli / 1000;
2548+ tv.tv_usec = (milli % 1000) * 1000;
2549+
2550+ if (select((int) max_fd + 1, &read_set, &write_set, NULL, &tv) > 0) {
2551+ // select() might have been waiting for a long time, reset current_time
2552+ // now to prevent last_io_time being set to the past.
2553+ current_time = time(NULL);
2554+
2555+ // Read wakeup messages
2556+ if (mgr->ctl[1] != INVALID_SOCKET &&
2557+ FD_ISSET(mgr->ctl[1], &read_set)) {
2558+ struct ctl_msg ctl_msg;
2559+ int len = (int) recv(mgr->ctl[1], (char *) &ctl_msg, sizeof(ctl_msg), 0);
2560+ send(mgr->ctl[1], ctl_msg.message, 1, 0);
2561+ if (len >= (int) sizeof(ctl_msg.callback) && ctl_msg.callback != NULL) {
2562+ struct ns_connection *c;
2563+ for (c = ns_next(mgr, NULL); c != NULL; c = ns_next(mgr, c)) {
2564+ ctl_msg.callback(c, NS_POLL, ctl_msg.message);
2565+ }
2566+ }
2567+ }
2568+
2569+ for (conn = mgr->active_connections; conn != NULL; conn = tmp_conn) {
2570+ tmp_conn = conn->next;
2571+ if (FD_ISSET(conn->sock, &read_set)) {
2572+ if (conn->flags & NSF_LISTENING) {
2573+ if (conn->flags & NSF_UDP) {
2574+ ns_handle_udp(conn);
2575+ } else {
2576+ // We're not looping here, and accepting just one connection at
2577+ // a time. The reason is that eCos does not respect non-blocking
2578+ // flag on a listening socket and hangs in a loop.
2579+ accept_conn(conn);
2580+ }
2581+ } else {
2582+ conn->last_io_time = current_time;
2583+ ns_read_from_socket(conn);
2584+ }
2585+ }
2586+
2587+ if (FD_ISSET(conn->sock, &write_set)) {
2588+ if (conn->flags & NSF_CONNECTING) {
2589+ ns_read_from_socket(conn);
2590+ } else if (!(conn->flags & NSF_BUFFER_BUT_DONT_SEND)) {
2591+ conn->last_io_time = current_time;
2592+ ns_write_to_socket(conn);
2593+ }
2594+ }
2595+ }
2596+ }
2597+
2598+ for (conn = mgr->active_connections; conn != NULL; conn = tmp_conn) {
2599+ tmp_conn = conn->next;
2600+ if ((conn->flags & NSF_CLOSE_IMMEDIATELY) ||
2601+ (conn->send_iobuf.len == 0 &&
2602+ (conn->flags & NSF_FINISHED_SENDING_DATA))) {
2603+ ns_close_conn(conn);
2604+ }
2605+ }
2606+
2607+ return current_time;
2608+}
2609+
2610+struct ns_connection *ns_connect(struct ns_mgr *mgr, const char *address,
2611+ ns_callback_t callback, void *user_data) {
2612+ sock_t sock = INVALID_SOCKET;
2613+ struct ns_connection *nc = NULL;
2614+ union socket_address sa;
2615+ char cert[100], ca_cert[100];
2616+ int rc, use_ssl, proto;
2617+
2618+ ns_parse_address(address, &sa, &proto, &use_ssl, cert, ca_cert);
2619+ if ((sock = socket(AF_INET, proto, 0)) == INVALID_SOCKET) {
2620+ return NULL;
2621+ }
2622+ ns_set_non_blocking_mode(sock);
2623+ rc = (proto == SOCK_DGRAM) ? 0 : connect(sock, &sa.sa, sizeof(sa.sin));
2624+
2625+ if (rc != 0 && ns_is_error(rc)) {
2626+ closesocket(sock);
2627+ return NULL;
2628+ } else if ((nc = ns_add_sock(mgr, sock, callback, user_data)) == NULL) {
2629+ closesocket(sock);
2630+ return NULL;
2631+ }
2632+
2633+ nc->sa = sa; // Important, cause UDP conns will use sendto()
2634+ nc->flags = (proto == SOCK_DGRAM) ? NSF_UDP : NSF_CONNECTING;
2635+
2636+#ifdef NS_ENABLE_SSL
2637+ if (use_ssl) {
2638+ if ((nc->ssl_ctx = SSL_CTX_new(SSLv23_client_method())) == NULL ||
2639+ ns_use_cert(nc->ssl_ctx, cert) != 0 ||
2640+ ns_use_ca_cert(nc->ssl_ctx, ca_cert) != 0 ||
2641+ (nc->ssl = SSL_new(nc->ssl_ctx)) == NULL) {
2642+ ns_close_conn(nc);
2643+ return NULL;
2644+ } else {
2645+ SSL_set_fd(nc->ssl, sock);
2646+ }
2647+ }
2648+#endif
2649+
2650+ return nc;
2651+}
2652+
2653+struct ns_connection *ns_add_sock(struct ns_mgr *s, sock_t sock,
2654+ ns_callback_t callback, void *user_data) {
2655+ struct ns_connection *conn;
2656+ if ((conn = (struct ns_connection *) NS_MALLOC(sizeof(*conn))) != NULL) {
2657+ memset(conn, 0, sizeof(*conn));
2658+ ns_set_non_blocking_mode(sock);
2659+ ns_set_close_on_exec(sock);
2660+ conn->sock = sock;
2661+ conn->user_data = user_data;
2662+ conn->callback = callback;
2663+ conn->mgr = s;
2664+ conn->last_io_time = time(NULL);
2665+ ns_add_conn(s, conn);
2666+ DBG(("%p %d", conn, sock));
2667+ }
2668+ return conn;
2669+}
2670+
2671+struct ns_connection *ns_next(struct ns_mgr *s, struct ns_connection *conn) {
2672+ return conn == NULL ? s->active_connections : conn->next;
2673+}
2674+
2675+void ns_broadcast(struct ns_mgr *mgr, ns_callback_t cb,void *data, size_t len) {
2676+ struct ctl_msg ctl_msg;
2677+ if (mgr->ctl[0] != INVALID_SOCKET && data != NULL &&
2678+ len < sizeof(ctl_msg.message)) {
2679+ ctl_msg.callback = cb;
2680+ memcpy(ctl_msg.message, data, len);
2681+ send(mgr->ctl[0], (char *) &ctl_msg,
2682+ offsetof(struct ctl_msg, message) + len, 0);
2683+ recv(mgr->ctl[0], (char *) &len, 1, 0);
2684+ }
2685+}
2686+
2687+void ns_mgr_init(struct ns_mgr *s, void *user_data) {
2688+ memset(s, 0, sizeof(*s));
2689+ s->ctl[0] = s->ctl[1] = INVALID_SOCKET;
2690+ s->user_data = user_data;
2691+
2692+#ifdef _WIN32
2693+ { WSADATA data; WSAStartup(MAKEWORD(2, 2), &data); }
2694+#else
2695+ // Ignore SIGPIPE signal, so if client cancels the request, it
2696+ // won't kill the whole process.
2697+ signal(SIGPIPE, SIG_IGN);
2698+#endif
2699+
2700+#ifndef NS_DISABLE_SOCKETPAIR
2701+ do {
2702+ ns_socketpair2(s->ctl, SOCK_DGRAM);
2703+ } while (s->ctl[0] == INVALID_SOCKET);
2704+#endif
2705+
2706+#ifdef NS_ENABLE_SSL
2707+ {static int init_done; if (!init_done) { SSL_library_init(); init_done++; }}
2708+#endif
2709+}
2710+
2711+void ns_mgr_free(struct ns_mgr *s) {
2712+ struct ns_connection *conn, *tmp_conn;
2713+
2714+ DBG(("%p", s));
2715+ if (s == NULL) return;
2716+ // Do one last poll, see https://github.com/cesanta/mongoose/issues/286
2717+ ns_mgr_poll(s, 0);
2718+
2719+ if (s->ctl[0] != INVALID_SOCKET) closesocket(s->ctl[0]);
2720+ if (s->ctl[1] != INVALID_SOCKET) closesocket(s->ctl[1]);
2721+ s->ctl[0] = s->ctl[1] = INVALID_SOCKET;
2722+
2723+ for (conn = s->active_connections; conn != NULL; conn = tmp_conn) {
2724+ tmp_conn = conn->next;
2725+ ns_close_conn(conn);
2726+ }
2727+}
2728+// net_skeleton end
2729+#endif // NOEMBED_NET_SKELETON
2730+
2731+#include <ctype.h>
2732+
2733+#ifdef _WIN32 //////////////// Windows specific defines and includes
2734+#include <io.h> // For _lseeki64
2735+#include <direct.h> // For _mkdir
2736+#ifndef S_ISDIR
2737+#define S_ISDIR(x) ((x) & _S_IFDIR)
2738+#endif
2739+#ifdef stat
2740+#undef stat
2741+#endif
2742+#ifdef lseek
2743+#undef lseek
2744+#endif
2745+#ifdef popen
2746+#undef popen
2747+#endif
2748+#ifdef pclose
2749+#undef pclose
2750+#endif
2751+#define stat(x, y) mg_stat((x), (y))
2752+#define fopen(x, y) mg_fopen((x), (y))
2753+#define open(x, y, z) mg_open((x), (y), (z))
2754+#define close(x) _close(x)
2755+#define lseek(x, y, z) _lseeki64((x), (y), (z))
2756+#define popen(x, y) _popen((x), (y))
2757+#define pclose(x) _pclose(x)
2758+#define mkdir(x, y) _mkdir(x)
2759+#ifndef __func__
2760+#define STRX(x) #x
2761+#define STR(x) STRX(x)
2762+#define __func__ __FILE__ ":" STR(__LINE__)
2763+#endif
2764+/* MINGW has adopted the MSVC formatting for 64-bit ints as of gcc 4.4 till 4.8*/
2765+#if (defined(__MINGW32__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 4 && __GNUC_MINOR__ < 8))) || defined(_MSC_VER)
2766+#define INT64_FMT "I64d"
2767+#else
2768+#define INT64_FMT "lld"
2769+#endif
2770+#define flockfile(x) ((void) (x))
2771+#define funlockfile(x) ((void) (x))
2772+typedef struct _stati64 file_stat_t;
2773+typedef HANDLE process_id_t;
2774+
2775+#else ////////////// UNIX specific defines and includes
2776+
2777+#include <dirent.h>
2778+#include <dlfcn.h>
2779+#include <inttypes.h>
2780+#include <pwd.h>
2781+#define O_BINARY 0
2782+#define INT64_FMT PRId64
2783+typedef struct stat file_stat_t;
2784+typedef pid_t process_id_t;
2785+#endif //////// End of platform-specific defines and includes
2786+
2787+#include "mongoose.h"
2788+
2789+#define MAX_REQUEST_SIZE 16384
2790+#define IOBUF_SIZE 8192
2791+#define MAX_PATH_SIZE 8192
2792+#define DEFAULT_CGI_PATTERN "**.cgi$|**.pl$|**.php$"
2793+#define CGI_ENVIRONMENT_SIZE 8192
2794+#define MAX_CGI_ENVIR_VARS 64
2795+#define ENV_EXPORT_TO_CGI "MONGOOSE_CGI"
2796+#define PASSWORDS_FILE_NAME ".htpasswd"
2797+
2798+#ifndef MONGOOSE_USE_WEBSOCKET_PING_INTERVAL
2799+#define MONGOOSE_USE_WEBSOCKET_PING_INTERVAL 5
2800+#endif
2801+
2802+// Extra HTTP headers to send in every static file reply
2803+#if !defined(MONGOOSE_USE_EXTRA_HTTP_HEADERS)
2804+#define MONGOOSE_USE_EXTRA_HTTP_HEADERS ""
2805+#endif
2806+
2807+#ifndef MONGOOSE_POST_SIZE_LIMIT
2808+#define MONGOOSE_POST_SIZE_LIMIT 0
2809+#endif
2810+
2811+#ifndef MONGOOSE_IDLE_TIMEOUT_SECONDS
2812+#define MONGOOSE_IDLE_TIMEOUT_SECONDS 300
2813+#endif
2814+
2815+#ifdef NS_DISABLE_SOCKETPAIR
2816+#define MONGOOSE_NO_CGI
2817+#endif
2818+
2819+#ifdef MONGOOSE_NO_FILESYSTEM
2820+#define MONGOOSE_NO_AUTH
2821+#define MONGOOSE_NO_CGI
2822+#define MONGOOSE_NO_DAV
2823+#define MONGOOSE_NO_DIRECTORY_LISTING
2824+#define MONGOOSE_NO_LOGGING
2825+#define MONGOOSE_NO_SSI
2826+#define MONGOOSE_NO_DL
2827+#endif
2828+
2829+struct vec {
2830+ const char *ptr;
2831+ int len;
2832+};
2833+
2834+// For directory listing and WevDAV support
2835+struct dir_entry {
2836+ struct connection *conn;
2837+ char *file_name;
2838+ file_stat_t st;
2839+};
2840+
2841+// NOTE(lsm): this enum shoulds be in sync with the config_options.
2842+enum {
2843+ ACCESS_CONTROL_LIST,
2844+#ifndef MONGOOSE_NO_FILESYSTEM
2845+ ACCESS_LOG_FILE,
2846+#ifndef MONGOOSE_NO_AUTH
2847+ AUTH_DOMAIN,
2848+#endif
2849+#ifndef MONGOOSE_NO_CGI
2850+ CGI_INTERPRETER,
2851+ CGI_PATTERN,
2852+#endif
2853+ DAV_AUTH_FILE,
2854+ DOCUMENT_ROOT,
2855+#ifndef MONGOOSE_NO_DIRECTORY_LISTING
2856+ ENABLE_DIRECTORY_LISTING,
2857+#endif
2858+#endif
2859+ ENABLE_PROXY,
2860+ EXTRA_MIME_TYPES,
2861+#if !defined(MONGOOSE_NO_FILESYSTEM) && !defined(MONGOOSE_NO_AUTH)
2862+ GLOBAL_AUTH_FILE,
2863+#endif
2864+#ifndef MONGOOSE_NO_FILESYSTEM
2865+ HIDE_FILES_PATTERN,
2866+ HEXDUMP_FILE,
2867+ INDEX_FILES,
2868+#endif
2869+ LISTENING_PORT,
2870+#ifndef _WIN32
2871+ RUN_AS_USER,
2872+#endif
2873+#ifndef MONGOOSE_NO_SSI
2874+ SSI_PATTERN,
2875+#endif
2876+ URL_REWRITES,
2877+ NUM_OPTIONS
2878+};
2879+
2880+static const char *static_config_options[] = {
2881+ "access_control_list", NULL,
2882+#ifndef MONGOOSE_NO_FILESYSTEM
2883+ "access_log_file", NULL,
2884+#ifndef MONGOOSE_NO_AUTH
2885+ "auth_domain", "mydomain.com",
2886+#endif
2887+#ifndef MONGOOSE_NO_CGI
2888+ "cgi_interpreter", NULL,
2889+ "cgi_pattern", DEFAULT_CGI_PATTERN,
2890+#endif
2891+ "dav_auth_file", NULL,
2892+ "document_root", NULL,
2893+#ifndef MONGOOSE_NO_DIRECTORY_LISTING
2894+ "enable_directory_listing", "yes",
2895+#endif
2896+#endif
2897+ "enable_proxy", NULL,
2898+ "extra_mime_types", NULL,
2899+#if !defined(MONGOOSE_NO_FILESYSTEM) && !defined(MONGOOSE_NO_AUTH)
2900+ "global_auth_file", NULL,
2901+#endif
2902+#ifndef MONGOOSE_NO_FILESYSTEM
2903+ "hide_files_patterns", NULL,
2904+ "hexdump_file", NULL,
2905+ "index_files","index.html,index.htm,index.shtml,index.cgi,index.php",
2906+#endif
2907+ "listening_port", NULL,
2908+#ifndef _WIN32
2909+ "run_as_user", NULL,
2910+#endif
2911+#ifndef MONGOOSE_NO_SSI
2912+ "ssi_pattern", "**.shtml$|**.shtm$",
2913+#endif
2914+ "url_rewrites", NULL,
2915+ NULL
2916+};
2917+
2918+struct mg_server {
2919+ struct ns_mgr ns_mgr;
2920+ union socket_address lsa; // Listening socket address
2921+ mg_handler_t event_handler;
2922+ char *config_options[NUM_OPTIONS];
2923+};
2924+
2925+// Local endpoint representation
2926+union endpoint {
2927+ int fd; // Opened regular local file
2928+ struct ns_connection *nc; // CGI or proxy->target connection
2929+};
2930+
2931+enum endpoint_type {
2932+ EP_NONE, EP_FILE, EP_CGI, EP_USER, EP_PUT, EP_CLIENT, EP_PROXY
2933+};
2934+
2935+#define MG_HEADERS_SENT NSF_USER_1
2936+#define MG_LONG_RUNNING NSF_USER_2
2937+#define MG_CGI_CONN NSF_USER_3
2938+#define MG_PROXY_CONN NSF_USER_4
2939+#define MG_PROXY_DONT_PARSE NSF_USER_5
2940+
2941+struct connection {
2942+ struct ns_connection *ns_conn; // NOTE(lsm): main.c depends on this order
2943+ struct mg_connection mg_conn;
2944+ struct mg_server *server;
2945+ union endpoint endpoint;
2946+ enum endpoint_type endpoint_type;
2947+ char *path_info;
2948+ char *request;
2949+ int64_t num_bytes_recv; // Total number of bytes received
2950+ int64_t cl; // Reply content length, for Range support
2951+ int request_len; // Request length, including last \r\n after last header
2952+};
2953+
2954+#define MG_CONN_2_CONN(c) ((struct connection *) ((char *) (c) - \
2955+ offsetof(struct connection, mg_conn)))
2956+
2957+static void open_local_endpoint(struct connection *conn, int skip_user);
2958+static void close_local_endpoint(struct connection *conn);
2959+static void mg_ev_handler(struct ns_connection *nc, int ev, void *p);
2960+
2961+static const struct {
2962+ const char *extension;
2963+ size_t ext_len;
2964+ const char *mime_type;
2965+} static_builtin_mime_types[] = {
2966+ {".html", 5, "text/html"},
2967+ {".htm", 4, "text/html"},
2968+ {".shtm", 5, "text/html"},
2969+ {".shtml", 6, "text/html"},
2970+ {".css", 4, "text/css"},
2971+ {".js", 3, "application/x-javascript"},
2972+ {".ico", 4, "image/x-icon"},
2973+ {".gif", 4, "image/gif"},
2974+ {".jpg", 4, "image/jpeg"},
2975+ {".jpeg", 5, "image/jpeg"},
2976+ {".png", 4, "image/png"},
2977+ {".svg", 4, "image/svg+xml"},
2978+ {".txt", 4, "text/plain"},
2979+ {".torrent", 8, "application/x-bittorrent"},
2980+ {".wav", 4, "audio/x-wav"},
2981+ {".mp3", 4, "audio/x-mp3"},
2982+ {".mid", 4, "audio/mid"},
2983+ {".m3u", 4, "audio/x-mpegurl"},
2984+ {".ogg", 4, "application/ogg"},
2985+ {".ram", 4, "audio/x-pn-realaudio"},
2986+ {".xml", 4, "text/xml"},
2987+ {".json", 5, "application/json"},
2988+ {".xslt", 5, "application/xml"},
2989+ {".xsl", 4, "application/xml"},
2990+ {".ra", 3, "audio/x-pn-realaudio"},
2991+ {".doc", 4, "application/msword"},
2992+ {".exe", 4, "application/octet-stream"},
2993+ {".zip", 4, "application/x-zip-compressed"},
2994+ {".xls", 4, "application/excel"},
2995+ {".tgz", 4, "application/x-tar-gz"},
2996+ {".tar", 4, "application/x-tar"},
2997+ {".gz", 3, "application/x-gunzip"},
2998+ {".arj", 4, "application/x-arj-compressed"},
2999+ {".rar", 4, "application/x-rar-compressed"},
3000+ {".rtf", 4, "application/rtf"},
3001+ {".pdf", 4, "application/pdf"},
3002+ {".swf", 4, "application/x-shockwave-flash"},
3003+ {".mpg", 4, "video/mpeg"},
3004+ {".webm", 5, "video/webm"},
3005+ {".mpeg", 5, "video/mpeg"},
3006+ {".mov", 4, "video/quicktime"},
3007+ {".mp4", 4, "video/mp4"},
3008+ {".m4v", 4, "video/x-m4v"},
3009+ {".asf", 4, "video/x-ms-asf"},
3010+ {".avi", 4, "video/x-msvideo"},
3011+ {".bmp", 4, "image/bmp"},
3012+ {".ttf", 4, "application/x-font-ttf"},
3013+ {NULL, 0, NULL}
3014+};
3015+
3016+#ifndef MONGOOSE_NO_THREADS
3017+void *mg_start_thread(void *(*f)(void *), void *p) {
3018+ return ns_start_thread(f, p);
3019+}
3020+#endif // MONGOOSE_NO_THREADS
3021+
3022+#ifndef MONGOOSE_NO_MMAP
3023+#ifdef _WIN32
3024+static void *mmap(void *addr, int64_t len, int prot, int flags, int fd,
3025+ int offset) {
3026+ HANDLE fh = (HANDLE) _get_osfhandle(fd);
3027+ HANDLE mh = CreateFileMapping(fh, 0, PAGE_READONLY, 0, 0, 0);
3028+ void *p = MapViewOfFile(mh, FILE_MAP_READ, 0, 0, (size_t) len);
3029+ CloseHandle(mh);
3030+ return p;
3031+}
3032+#define munmap(x, y) UnmapViewOfFile(x)
3033+#define MAP_FAILED NULL
3034+#define MAP_PRIVATE 0
3035+#define PROT_READ 0
3036+#else
3037+#include <sys/mman.h>
3038+#endif
3039+
3040+void *mg_mmap(FILE *fp, size_t size) {
3041+ void *p = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fileno(fp), 0);
3042+ return p == MAP_FAILED ? NULL : p;
3043+}
3044+
3045+void mg_munmap(void *p, size_t size) {
3046+ munmap(p, size);
3047+}
3048+#endif // MONGOOSE_NO_MMAP
3049+
3050+#if defined(_WIN32) && !defined(MONGOOSE_NO_FILESYSTEM)
3051+// Encode 'path' which is assumed UTF-8 string, into UNICODE string.
3052+// wbuf and wbuf_len is a target buffer and its length.
3053+static void to_wchar(const char *path, wchar_t *wbuf, size_t wbuf_len) {
3054+ char buf[MAX_PATH_SIZE * 2], buf2[MAX_PATH_SIZE * 2], *p;
3055+
3056+ strncpy(buf, path, sizeof(buf));
3057+ buf[sizeof(buf) - 1] = '\0';
3058+
3059+ // Trim trailing slashes. Leave backslash for paths like "X:\"
3060+ p = buf + strlen(buf) - 1;
3061+ while (p > buf && p[-1] != ':' && (p[0] == '\\' || p[0] == '/')) *p-- = '\0';
3062+
3063+ // Convert to Unicode and back. If doubly-converted string does not
3064+ // match the original, something is fishy, reject.
3065+ memset(wbuf, 0, wbuf_len * sizeof(wchar_t));
3066+ MultiByteToWideChar(CP_UTF8, 0, buf, -1, wbuf, (int) wbuf_len);
3067+ WideCharToMultiByte(CP_UTF8, 0, wbuf, (int) wbuf_len, buf2, sizeof(buf2),
3068+ NULL, NULL);
3069+ if (strcmp(buf, buf2) != 0) {
3070+ wbuf[0] = L'\0';
3071+ }
3072+}
3073+
3074+static int mg_stat(const char *path, file_stat_t *st) {
3075+ wchar_t wpath[MAX_PATH_SIZE];
3076+ to_wchar(path, wpath, ARRAY_SIZE(wpath));
3077+ DBG(("[%ls] -> %d", wpath, _wstati64(wpath, st)));
3078+ return _wstati64(wpath, st);
3079+}
3080+
3081+static FILE *mg_fopen(const char *path, const char *mode) {
3082+ wchar_t wpath[MAX_PATH_SIZE], wmode[10];
3083+ to_wchar(path, wpath, ARRAY_SIZE(wpath));
3084+ to_wchar(mode, wmode, ARRAY_SIZE(wmode));
3085+ return _wfopen(wpath, wmode);
3086+}
3087+
3088+static int mg_open(const char *path, int flag, int mode) {
3089+ wchar_t wpath[MAX_PATH_SIZE];
3090+ to_wchar(path, wpath, ARRAY_SIZE(wpath));
3091+ return _wopen(wpath, flag, mode);
3092+}
3093+#endif // _WIN32 && !MONGOOSE_NO_FILESYSTEM
3094+
3095+// A helper function for traversing a comma separated list of values.
3096+// It returns a list pointer shifted to the next value, or NULL if the end
3097+// of the list found.
3098+// Value is stored in val vector. If value has form "x=y", then eq_val
3099+// vector is initialized to point to the "y" part, and val vector length
3100+// is adjusted to point only to "x".
3101+static const char *next_option(const char *list, struct vec *val,
3102+ struct vec *eq_val) {
3103+ if (list == NULL || *list == '\0') {
3104+ // End of the list
3105+ list = NULL;
3106+ } else {
3107+ val->ptr = list;
3108+ if ((list = strchr(val->ptr, ',')) != NULL) {
3109+ // Comma found. Store length and shift the list ptr
3110+ val->len = list - val->ptr;
3111+ list++;
3112+ } else {
3113+ // This value is the last one
3114+ list = val->ptr + strlen(val->ptr);
3115+ val->len = list - val->ptr;
3116+ }
3117+
3118+ if (eq_val != NULL) {
3119+ // Value has form "x=y", adjust pointers and lengths
3120+ // so that val points to "x", and eq_val points to "y".
3121+ eq_val->len = 0;
3122+ eq_val->ptr = (const char *) memchr(val->ptr, '=', val->len);
3123+ if (eq_val->ptr != NULL) {
3124+ eq_val->ptr++; // Skip over '=' character
3125+ eq_val->len = val->ptr + val->len - eq_val->ptr;
3126+ val->len = (eq_val->ptr - val->ptr) - 1;
3127+ }
3128+ }
3129+ }
3130+
3131+ return list;
3132+}
3133+
3134+// Like snprintf(), but never returns negative value, or a value
3135+// that is larger than a supplied buffer.
3136+static int mg_vsnprintf(char *buf, size_t buflen, const char *fmt, va_list ap) {
3137+ int n;
3138+ if (buflen < 1) return 0;
3139+ n = vsnprintf(buf, buflen, fmt, ap);
3140+ if (n < 0) {
3141+ n = 0;
3142+ } else if (n >= (int) buflen) {
3143+ n = (int) buflen - 1;
3144+ }
3145+ buf[n] = '\0';
3146+ return n;
3147+}
3148+
3149+static int mg_snprintf(char *buf, size_t buflen, const char *fmt, ...) {
3150+ va_list ap;
3151+ int n;
3152+ va_start(ap, fmt);
3153+ n = mg_vsnprintf(buf, buflen, fmt, ap);
3154+ va_end(ap);
3155+ return n;
3156+}
3157+
3158+// Check whether full request is buffered. Return:
3159+// -1 if request is malformed
3160+// 0 if request is not yet fully buffered
3161+// >0 actual request length, including last \r\n\r\n
3162+static int get_request_len(const char *s, int buf_len) {
3163+ const unsigned char *buf = (unsigned char *) s;
3164+ int i;
3165+
3166+ for (i = 0; i < buf_len; i++) {
3167+ // Control characters are not allowed but >=128 are.
3168+ // Abort scan as soon as one malformed character is found.
3169+ if (!isprint(buf[i]) && buf[i] != '\r' && buf[i] != '\n' && buf[i] < 128) {
3170+ return -1;
3171+ } else if (buf[i] == '\n' && i + 1 < buf_len && buf[i + 1] == '\n') {
3172+ return i + 2;
3173+ } else if (buf[i] == '\n' && i + 2 < buf_len && buf[i + 1] == '\r' &&
3174+ buf[i + 2] == '\n') {
3175+ return i + 3;
3176+ }
3177+ }
3178+
3179+ return 0;
3180+}
3181+
3182+// Skip the characters until one of the delimiters characters found.
3183+// 0-terminate resulting word. Skip the rest of the delimiters if any.
3184+// Advance pointer to buffer to the next word. Return found 0-terminated word.
3185+static char *skip(char **buf, const char *delimiters) {
3186+ char *p, *begin_word, *end_word, *end_delimiters;
3187+
3188+ begin_word = *buf;
3189+ end_word = begin_word + strcspn(begin_word, delimiters);
3190+ end_delimiters = end_word + strspn(end_word, delimiters);
3191+
3192+ for (p = end_word; p < end_delimiters; p++) {
3193+ *p = '\0';
3194+ }
3195+
3196+ *buf = end_delimiters;
3197+
3198+ return begin_word;
3199+}
3200+
3201+// Parse HTTP headers from the given buffer, advance buffer to the point
3202+// where parsing stopped.
3203+static void parse_http_headers(char **buf, struct mg_connection *ri) {
3204+ size_t i;
3205+
3206+ for (i = 0; i < ARRAY_SIZE(ri->http_headers); i++) {
3207+ ri->http_headers[i].name = skip(buf, ": ");
3208+ ri->http_headers[i].value = skip(buf, "\r\n");
3209+ if (ri->http_headers[i].name[0] == '\0')
3210+ break;
3211+ ri->num_headers = i + 1;
3212+ }
3213+}
3214+
3215+static const char *status_code_to_str(int status_code) {
3216+ switch (status_code) {
3217+
3218+ case 100: return "Continue";
3219+ case 101: return "Switching Protocols";
3220+ case 102: return "Processing";
3221+
3222+ case 200: return "OK";
3223+ case 201: return "Created";
3224+ case 202: return "Accepted";
3225+ case 203: return "Non-Authoritative Information";
3226+ case 204: return "No Content";
3227+ case 205: return "Reset Content";
3228+ case 206: return "Partial Content";
3229+ case 207: return "Multi-Status";
3230+ case 208: return "Already Reported";
3231+ case 226: return "IM Used";
3232+
3233+ case 300: return "Multiple Choices";
3234+ case 301: return "Moved Permanently";
3235+ case 302: return "Found";
3236+ case 303: return "See Other";
3237+ case 304: return "Not Modified";
3238+ case 305: return "Use Proxy";
3239+ case 306: return "Switch Proxy";
3240+ case 307: return "Temporary Redirect";
3241+ case 308: return "Permanent Redirect";
3242+
3243+ case 400: return "Bad Request";
3244+ case 401: return "Unauthorized";
3245+ case 402: return "Payment Required";
3246+ case 403: return "Forbidden";
3247+ case 404: return "Not Found";
3248+ case 405: return "Method Not Allowed";
3249+ case 406: return "Not Acceptable";
3250+ case 407: return "Proxy Authentication Required";
3251+ case 408: return "Request Timeout";
3252+ case 409: return "Conflict";
3253+ case 410: return "Gone";
3254+ case 411: return "Length Required";
3255+ case 412: return "Precondition Failed";
3256+ case 413: return "Payload Too Large";
3257+ case 414: return "URI Too Long";
3258+ case 415: return "Unsupported Media Type";
3259+ case 416: return "Requested Range Not Satisfiable";
3260+ case 417: return "Expectation Failed";
3261+ case 418: return "I\'m a teapot";
3262+ case 422: return "Unprocessable Entity";
3263+ case 423: return "Locked";
3264+ case 424: return "Failed Dependency";
3265+ case 426: return "Upgrade Required";
3266+ case 428: return "Precondition Required";
3267+ case 429: return "Too Many Requests";
3268+ case 431: return "Request Header Fields Too Large";
3269+ case 451: return "Unavailable For Legal Reasons";
3270+
3271+ case 500: return "Internal Server Error";
3272+ case 501: return "Not Implemented";
3273+ case 502: return "Bad Gateway";
3274+ case 503: return "Service Unavailable";
3275+ case 504: return "Gateway Timeout";
3276+ case 505: return "HTTP Version Not Supported";
3277+ case 506: return "Variant Also Negotiates";
3278+ case 507: return "Insufficient Storage";
3279+ case 508: return "Loop Detected";
3280+ case 510: return "Not Extended";
3281+ case 511: return "Network Authentication Required";
3282+
3283+ default: return "Server Error";
3284+ }
3285+}
3286+
3287+static int call_user(struct connection *conn, enum mg_event ev) {
3288+ return conn != NULL && conn->server != NULL &&
3289+ conn->server->event_handler != NULL ?
3290+ conn->server->event_handler(&conn->mg_conn, ev) : MG_FALSE;
3291+}
3292+
3293+static void send_http_error(struct connection *conn, int code,
3294+ const char *fmt, ...) {
3295+ const char *message = status_code_to_str(code);
3296+ const char *rewrites = conn->server->config_options[URL_REWRITES];
3297+ char headers[200], body[200];
3298+ struct vec a, b;
3299+ va_list ap;
3300+ int body_len, headers_len, match_code;
3301+
3302+ conn->mg_conn.status_code = code;
3303+
3304+ // Invoke error handler if it is set
3305+ if (call_user(conn, MG_HTTP_ERROR) == MG_TRUE) {
3306+ close_local_endpoint(conn);
3307+ return;
3308+ }
3309+
3310+ // Handle error code rewrites
3311+ while ((rewrites = next_option(rewrites, &a, &b)) != NULL) {
3312+ if ((match_code = atoi(a.ptr)) > 0 && match_code == code) {
3313+ struct mg_connection *c = &conn->mg_conn;
3314+ c->status_code = 302;
3315+ mg_printf(c, "HTTP/1.1 %d Moved\r\n"
3316+ "Location: %.*s?code=%d&orig_uri=%s&query_string=%s\r\n\r\n",
3317+ c->status_code, b.len, b.ptr, code, c->uri,
3318+ c->query_string == NULL ? "" : c->query_string);
3319+ close_local_endpoint(conn);
3320+ return;
3321+ }
3322+ }
3323+
3324+ body_len = mg_snprintf(body, sizeof(body), "%d %s\n", code, message);
3325+ if (fmt != NULL) {
3326+ va_start(ap, fmt);
3327+ body_len += mg_vsnprintf(body + body_len, sizeof(body) - body_len, fmt, ap);
3328+ va_end(ap);
3329+ }
3330+ if ((code >= 300 && code <= 399) || code == 204) {
3331+ // 3xx errors do not have body
3332+ body_len = 0;
3333+ }
3334+ headers_len = mg_snprintf(headers, sizeof(headers),
3335+ "HTTP/1.1 %d %s\r\nContent-Length: %d\r\n"
3336+ "Content-Type: text/plain\r\n\r\n",
3337+ code, message, body_len);
3338+ ns_send(conn->ns_conn, headers, headers_len);
3339+ ns_send(conn->ns_conn, body, body_len);
3340+ close_local_endpoint(conn); // This will write to the log file
3341+}
3342+
3343+static void write_chunk(struct connection *conn, const char *buf, int len) {
3344+ char chunk_size[50];
3345+ int n = mg_snprintf(chunk_size, sizeof(chunk_size), "%X\r\n", len);
3346+ ns_send(conn->ns_conn, chunk_size, n);
3347+ ns_send(conn->ns_conn, buf, len);
3348+ ns_send(conn->ns_conn, "\r\n", 2);
3349+}
3350+
3351+size_t mg_printf(struct mg_connection *conn, const char *fmt, ...) {
3352+ struct connection *c = MG_CONN_2_CONN(conn);
3353+ va_list ap;
3354+
3355+ va_start(ap, fmt);
3356+ ns_vprintf(c->ns_conn, fmt, ap);
3357+ va_end(ap);
3358+
3359+ return c->ns_conn->send_iobuf.len;
3360+}
3361+
3362+static void ns_forward(struct ns_connection *from, struct ns_connection *to) {
3363+ DBG(("%p -> %p %lu bytes", from, to, (unsigned long)from->recv_iobuf.len));
3364+ ns_send(to, from->recv_iobuf.buf, from->recv_iobuf.len);
3365+ iobuf_remove(&from->recv_iobuf, from->recv_iobuf.len);
3366+}
3367+
3368+#ifndef MONGOOSE_NO_CGI
3369+#ifdef _WIN32
3370+struct threadparam {
3371+ sock_t s;
3372+ HANDLE hPipe;
3373+};
3374+
3375+static int wait_until_ready(sock_t sock, int for_read) {
3376+ fd_set set;
3377+ FD_ZERO(&set);
3378+ FD_SET(sock, &set);
3379+ select(sock + 1, for_read ? &set : 0, for_read ? 0 : &set, 0, 0);
3380+ return 1;
3381+}
3382+
3383+static void *push_to_stdin(void *arg) {
3384+ struct threadparam *tp = (struct threadparam *)arg;
3385+ int n, sent, stop = 0;
3386+ DWORD k;
3387+ char buf[IOBUF_SIZE];
3388+
3389+ while (!stop && wait_until_ready(tp->s, 1) &&
3390+ (n = recv(tp->s, buf, sizeof(buf), 0)) > 0) {
3391+ if (n == -1 && GetLastError() == WSAEWOULDBLOCK) continue;
3392+ for (sent = 0; !stop && sent < n; sent += k) {
3393+ if (!WriteFile(tp->hPipe, buf + sent, n - sent, &k, 0)) stop = 1;
3394+ }
3395+ }
3396+ DBG(("%s", "FORWARED EVERYTHING TO CGI"));
3397+ CloseHandle(tp->hPipe);
3398+ free(tp);
3399+ _endthread();
3400+ return NULL;
3401+}
3402+
3403+static void *pull_from_stdout(void *arg) {
3404+ struct threadparam *tp = (struct threadparam *)arg;
3405+ int k = 0, stop = 0;
3406+ DWORD n, sent;
3407+ char buf[IOBUF_SIZE];
3408+
3409+ while (!stop && ReadFile(tp->hPipe, buf, sizeof(buf), &n, NULL)) {
3410+ for (sent = 0; !stop && sent < n; sent += k) {
3411+ if (wait_until_ready(tp->s, 0) &&
3412+ (k = send(tp->s, buf + sent, n - sent, 0)) <= 0) stop = 1;
3413+ }
3414+ }
3415+ DBG(("%s", "EOF FROM CGI"));
3416+ CloseHandle(tp->hPipe);
3417+ shutdown(tp->s, 2); // Without this, IO thread may get truncated data
3418+ closesocket(tp->s);
3419+ free(tp);
3420+ _endthread();
3421+ return NULL;
3422+}
3423+
3424+static void spawn_stdio_thread(sock_t sock, HANDLE hPipe,
3425+ void *(*func)(void *)) {
3426+ struct threadparam *tp = (struct threadparam *)malloc(sizeof(*tp));
3427+ if (tp != NULL) {
3428+ tp->s = sock;
3429+ tp->hPipe = hPipe;
3430+ mg_start_thread(func, tp);
3431+ }
3432+}
3433+
3434+static void abs_path(const char *utf8_path, char *abs_path, size_t len) {
3435+ wchar_t buf[MAX_PATH_SIZE], buf2[MAX_PATH_SIZE];
3436+ to_wchar(utf8_path, buf, ARRAY_SIZE(buf));
3437+ GetFullPathNameW(buf, ARRAY_SIZE(buf2), buf2, NULL);
3438+ WideCharToMultiByte(CP_UTF8, 0, buf2, wcslen(buf2) + 1, abs_path, len, 0, 0);
3439+}
3440+
3441+static process_id_t start_process(char *interp, const char *cmd,
3442+ const char *env, const char *envp[],
3443+ const char *dir, sock_t sock) {
3444+ STARTUPINFOW si;
3445+ PROCESS_INFORMATION pi;
3446+ HANDLE a[2], b[2], me = GetCurrentProcess();
3447+ wchar_t wcmd[MAX_PATH_SIZE], full_dir[MAX_PATH_SIZE];
3448+ char buf[MAX_PATH_SIZE], buf4[MAX_PATH_SIZE], buf5[MAX_PATH_SIZE],
3449+ cmdline[MAX_PATH_SIZE], *p;
3450+ DWORD flags = DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS;
3451+ FILE *fp;
3452+
3453+ memset(&si, 0, sizeof(si));
3454+ memset(&pi, 0, sizeof(pi));
3455+
3456+ si.cb = sizeof(si);
3457+ si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;
3458+ si.wShowWindow = SW_HIDE;
3459+ si.hStdError = GetStdHandle(STD_ERROR_HANDLE);
3460+
3461+ CreatePipe(&a[0], &a[1], NULL, 0);
3462+ CreatePipe(&b[0], &b[1], NULL, 0);
3463+ DuplicateHandle(me, a[0], me, &si.hStdInput, 0, TRUE, flags);
3464+ DuplicateHandle(me, b[1], me, &si.hStdOutput, 0, TRUE, flags);
3465+
3466+ if (interp == NULL && (fp = fopen(cmd, "r")) != NULL) {
3467+ buf[0] = buf[1] = '\0';
3468+ fgets(buf, sizeof(buf), fp);
3469+ buf[sizeof(buf) - 1] = '\0';
3470+ if (buf[0] == '#' && buf[1] == '!') {
3471+ interp = buf + 2;
3472+ for (p = interp + strlen(interp) - 1;
3473+ isspace(* (uint8_t *) p) && p > interp; p--) *p = '\0';
3474+ }
3475+ fclose(fp);
3476+ }
3477+
3478+ if (interp != NULL) {
3479+ abs_path(interp, buf4, ARRAY_SIZE(buf4));
3480+ interp = buf4;
3481+ }
3482+ abs_path(dir, buf5, ARRAY_SIZE(buf5));
3483+ to_wchar(dir, full_dir, ARRAY_SIZE(full_dir));
3484+ mg_snprintf(cmdline, sizeof(cmdline), "%s%s\"%s\"",
3485+ interp ? interp : "", interp ? " " : "", cmd);
3486+ to_wchar(cmdline, wcmd, ARRAY_SIZE(wcmd));
3487+
3488+ if (CreateProcessW(NULL, wcmd, NULL, NULL, TRUE, CREATE_NEW_PROCESS_GROUP,
3489+ (void *) env, full_dir, &si, &pi) != 0) {
3490+ spawn_stdio_thread(sock, a[1], push_to_stdin);
3491+ spawn_stdio_thread(sock, b[0], pull_from_stdout);
3492+ } else {
3493+ CloseHandle(a[1]);
3494+ CloseHandle(b[0]);
3495+ closesocket(sock);
3496+ }
3497+ DBG(("CGI command: [%ls] -> %p", wcmd, pi.hProcess));
3498+
3499+ // Not closing a[0] and b[1] because we've used DUPLICATE_CLOSE_SOURCE
3500+ CloseHandle(si.hStdOutput);
3501+ CloseHandle(si.hStdInput);
3502+ //CloseHandle(pi.hThread);
3503+ //CloseHandle(pi.hProcess);
3504+
3505+ return pi.hProcess;
3506+}
3507+#else
3508+static process_id_t start_process(const char *interp, const char *cmd,
3509+ const char *env, const char *envp[],
3510+ const char *dir, sock_t sock) {
3511+ char buf[500];
3512+ process_id_t pid = fork();
3513+ (void) env;
3514+
3515+ if (pid == 0) {
3516+ if (chdir(dir) == -1)
3517+ exit(EXIT_FAILURE);
3518+ (void) dup2(sock, 0);
3519+ (void) dup2(sock, 1);
3520+ closesocket(sock);
3521+
3522+ // After exec, all signal handlers are restored to their default values,
3523+ // with one exception of SIGCHLD. According to POSIX.1-2001 and Linux's
3524+ // implementation, SIGCHLD's handler will leave unchanged after exec
3525+ // if it was set to be ignored. Restore it to default action.
3526+ signal(SIGCHLD, SIG_DFL);
3527+
3528+ if (interp == NULL) {
3529+ execle(cmd, cmd, (char *) 0, envp); // Using (char *) 0 to avoid warning
3530+ } else {
3531+ execle(interp, interp, cmd, (char *) 0, envp);
3532+ }
3533+ snprintf(buf, sizeof(buf), "Status: 500\r\n\r\n"
3534+ "500 Server Error: %s%s%s: %s", interp == NULL ? "" : interp,
3535+ interp == NULL ? "" : " ", cmd, strerror(errno));
3536+ send(1, buf, strlen(buf), 0);
3537+ exit(EXIT_FAILURE); // exec call failed
3538+ }
3539+
3540+ return pid;
3541+}
3542+#endif // _WIN32
3543+
3544+// This structure helps to create an environment for the spawned CGI program.
3545+// Environment is an array of "VARIABLE=VALUE\0" ASCIIZ strings,
3546+// last element must be NULL.
3547+// However, on Windows there is a requirement that all these VARIABLE=VALUE\0
3548+// strings must reside in a contiguous buffer. The end of the buffer is
3549+// marked by two '\0' characters.
3550+// We satisfy both worlds: we create an envp array (which is vars), all
3551+// entries are actually pointers inside buf.
3552+struct cgi_env_block {
3553+ struct mg_connection *conn;
3554+ char buf[CGI_ENVIRONMENT_SIZE]; // Environment buffer
3555+ const char *vars[MAX_CGI_ENVIR_VARS]; // char *envp[]
3556+ int len; // Space taken
3557+ int nvars; // Number of variables in envp[]
3558+};
3559+
3560+// Append VARIABLE=VALUE\0 string to the buffer, and add a respective
3561+// pointer into the vars array.
3562+static char *addenv(struct cgi_env_block *block, const char *fmt, ...) {
3563+ int n, space;
3564+ char *added;
3565+ va_list ap;
3566+
3567+ // Calculate how much space is left in the buffer
3568+ space = sizeof(block->buf) - block->len - 2;
3569+ assert(space >= 0);
3570+
3571+ // Make a pointer to the free space int the buffer
3572+ added = block->buf + block->len;
3573+
3574+ // Copy VARIABLE=VALUE\0 string into the free space
3575+ va_start(ap, fmt);
3576+ n = mg_vsnprintf(added, (size_t) space, fmt, ap);
3577+ va_end(ap);
3578+
3579+ // Make sure we do not overflow buffer and the envp array
3580+ if (n > 0 && n + 1 < space &&
3581+ block->nvars < (int) ARRAY_SIZE(block->vars) - 2) {
3582+ // Append a pointer to the added string into the envp array
3583+ block->vars[block->nvars++] = added;
3584+ // Bump up used length counter. Include \0 terminator
3585+ block->len += n + 1;
3586+ }
3587+
3588+ return added;
3589+}
3590+
3591+static void addenv2(struct cgi_env_block *blk, const char *name) {
3592+ const char *s;
3593+ if ((s = getenv(name)) != NULL) addenv(blk, "%s=%s", name, s);
3594+}
3595+
3596+static void prepare_cgi_environment(struct connection *conn,
3597+ const char *prog,
3598+ struct cgi_env_block *blk) {
3599+ struct mg_connection *ri = &conn->mg_conn;
3600+ const char *s, *slash;
3601+ char *p, **opts = conn->server->config_options;
3602+ int i;
3603+
3604+ blk->len = blk->nvars = 0;
3605+ blk->conn = ri;
3606+
3607+ if ((s = getenv("SERVER_NAME")) != NULL) {
3608+ addenv(blk, "SERVER_NAME=%s", s);
3609+ } else {
3610+ addenv(blk, "SERVER_NAME=%s", ri->local_ip);
3611+ }
3612+ addenv(blk, "SERVER_ROOT=%s", opts[DOCUMENT_ROOT]);
3613+ addenv(blk, "DOCUMENT_ROOT=%s", opts[DOCUMENT_ROOT]);
3614+ addenv(blk, "SERVER_SOFTWARE=%s/%s", "Mongoose", MONGOOSE_VERSION);
3615+
3616+ // Prepare the environment block
3617+ addenv(blk, "%s", "GATEWAY_INTERFACE=CGI/1.1");
3618+ addenv(blk, "%s", "SERVER_PROTOCOL=HTTP/1.1");
3619+ addenv(blk, "%s", "REDIRECT_STATUS=200"); // For PHP
3620+
3621+ // TODO(lsm): fix this for IPv6 case
3622+ //addenv(blk, "SERVER_PORT=%d", ri->remote_port);
3623+
3624+ addenv(blk, "REQUEST_METHOD=%s", ri->request_method);
3625+ addenv(blk, "REMOTE_ADDR=%s", ri->remote_ip);
3626+ addenv(blk, "REMOTE_PORT=%d", ri->remote_port);
3627+ addenv(blk, "REQUEST_URI=%s%s%s", ri->uri,
3628+ ri->query_string == NULL ? "" : "?",
3629+ ri->query_string == NULL ? "" : ri->query_string);
3630+
3631+ // SCRIPT_NAME
3632+ if (conn->path_info != NULL) {
3633+ addenv(blk, "SCRIPT_NAME=%.*s",
3634+ (int) (strlen(ri->uri) - strlen(conn->path_info)), ri->uri);
3635+ addenv(blk, "PATH_INFO=%s", conn->path_info);
3636+ } else {
3637+ s = strrchr(prog, '/');
3638+ slash = strrchr(ri->uri, '/');
3639+ addenv(blk, "SCRIPT_NAME=%.*s%s",
3640+ slash == NULL ? 0 : (int) (slash - ri->uri), ri->uri,
3641+ s == NULL ? prog : s);
3642+ }
3643+
3644+ addenv(blk, "SCRIPT_FILENAME=%s", prog);
3645+ addenv(blk, "PATH_TRANSLATED=%s", prog);
3646+ addenv(blk, "HTTPS=%s", conn->ns_conn->ssl != NULL ? "on" : "off");
3647+
3648+ if ((s = mg_get_header(ri, "Content-Type")) != NULL)
3649+ addenv(blk, "CONTENT_TYPE=%s", s);
3650+
3651+ if (ri->query_string != NULL)
3652+ addenv(blk, "QUERY_STRING=%s", ri->query_string);
3653+
3654+ if ((s = mg_get_header(ri, "Content-Length")) != NULL)
3655+ addenv(blk, "CONTENT_LENGTH=%s", s);
3656+
3657+ addenv2(blk, "PATH");
3658+ addenv2(blk, "TMP");
3659+ addenv2(blk, "TEMP");
3660+ addenv2(blk, "TMPDIR");
3661+ addenv2(blk, "PERLLIB");
3662+ addenv2(blk, ENV_EXPORT_TO_CGI);
3663+
3664+#if defined(_WIN32)
3665+ addenv2(blk, "COMSPEC");
3666+ addenv2(blk, "SYSTEMROOT");
3667+ addenv2(blk, "SystemDrive");
3668+ addenv2(blk, "ProgramFiles");
3669+ addenv2(blk, "ProgramFiles(x86)");
3670+ addenv2(blk, "CommonProgramFiles(x86)");
3671+#else
3672+ addenv2(blk, "LD_LIBRARY_PATH");
3673+#endif // _WIN32
3674+
3675+ // Add all headers as HTTP_* variables
3676+ for (i = 0; i < ri->num_headers; i++) {
3677+ p = addenv(blk, "HTTP_%s=%s",
3678+ ri->http_headers[i].name, ri->http_headers[i].value);
3679+
3680+ // Convert variable name into uppercase, and change - to _
3681+ for (; *p != '=' && *p != '\0'; p++) {
3682+ if (*p == '-')
3683+ *p = '_';
3684+ *p = (char) toupper(* (unsigned char *) p);
3685+ }
3686+ }
3687+
3688+ blk->vars[blk->nvars++] = NULL;
3689+ blk->buf[blk->len++] = '\0';
3690+
3691+ assert(blk->nvars < (int) ARRAY_SIZE(blk->vars));
3692+ assert(blk->len > 0);
3693+ assert(blk->len < (int) sizeof(blk->buf));
3694+}
3695+
3696+static const char cgi_status[] = "HTTP/1.1 200 OK\r\n";
3697+
3698+static void open_cgi_endpoint(struct connection *conn, const char *prog) {
3699+ struct cgi_env_block blk;
3700+ char dir[MAX_PATH_SIZE];
3701+ const char *p;
3702+ sock_t fds[2];
3703+
3704+ prepare_cgi_environment(conn, prog, &blk);
3705+ // CGI must be executed in its own directory. 'dir' must point to the
3706+ // directory containing executable program, 'p' must point to the
3707+ // executable program name relative to 'dir'.
3708+ if ((p = strrchr(prog, '/')) == NULL) {
3709+ mg_snprintf(dir, sizeof(dir), "%s", ".");
3710+ } else {
3711+ mg_snprintf(dir, sizeof(dir), "%.*s", (int) (p - prog), prog);
3712+ }
3713+
3714+ // Try to create socketpair in a loop until success. ns_socketpair()
3715+ // can be interrupted by a signal and fail.
3716+ // TODO(lsm): use sigaction to restart interrupted syscall
3717+ do {
3718+ ns_socketpair(fds);
3719+ } while (fds[0] == INVALID_SOCKET);
3720+
3721+ if (start_process(conn->server->config_options[CGI_INTERPRETER],
3722+ prog, blk.buf, blk.vars, dir, fds[1]) != 0) {
3723+ conn->endpoint_type = EP_CGI;
3724+ conn->endpoint.nc = ns_add_sock(&conn->server->ns_mgr, fds[0],
3725+ mg_ev_handler, conn);
3726+ conn->endpoint.nc->flags |= MG_CGI_CONN;
3727+ ns_send(conn->ns_conn, cgi_status, sizeof(cgi_status) - 1);
3728+ conn->mg_conn.status_code = 200;
3729+ conn->ns_conn->flags |= NSF_BUFFER_BUT_DONT_SEND;
3730+ // Pass POST data to the CGI process
3731+ conn->endpoint.nc->send_iobuf = conn->ns_conn->recv_iobuf;
3732+ iobuf_init(&conn->ns_conn->recv_iobuf, 0);
3733+ } else {
3734+ closesocket(fds[0]);
3735+ send_http_error(conn, 500, "start_process(%s) failed", prog);
3736+ }
3737+
3738+#ifndef _WIN32
3739+ closesocket(fds[1]); // On Windows, CGI stdio thread closes that socket
3740+#endif
3741+}
3742+
3743+static void on_cgi_data(struct ns_connection *nc) {
3744+ struct connection *conn = (struct connection *) nc->user_data;
3745+ const char *status = "500";
3746+ struct mg_connection c;
3747+
3748+ if (!conn) return;
3749+
3750+ // Copy CGI data from CGI socket to the client send buffer
3751+ ns_forward(nc, conn->ns_conn);
3752+
3753+ // If reply has not been parsed yet, parse it
3754+ if (conn->ns_conn->flags & NSF_BUFFER_BUT_DONT_SEND) {
3755+ struct iobuf *io = &conn->ns_conn->send_iobuf;
3756+ int s_len = sizeof(cgi_status) - 1;
3757+ int len = get_request_len(io->buf + s_len, io->len - s_len);
3758+ char buf[MAX_REQUEST_SIZE], *s = buf;
3759+
3760+ if (len == 0) return;
3761+
3762+ if (len < 0 || len > (int) sizeof(buf)) {
3763+ len = io->len;
3764+ iobuf_remove(io, io->len);
3765+ send_http_error(conn, 500, "CGI program sent malformed headers: [%.*s]",
3766+ len, io->buf);
3767+ } else {
3768+ memset(&c, 0, sizeof(c));
3769+ memcpy(buf, io->buf + s_len, len);
3770+ buf[len - 1] = '\0';
3771+ parse_http_headers(&s, &c);
3772+ if (mg_get_header(&c, "Location") != NULL) {
3773+ status = "302";
3774+ } else if ((status = (char *) mg_get_header(&c, "Status")) == NULL) {
3775+ status = "200";
3776+ }
3777+ memcpy(io->buf + 9, status, 3);
3778+ conn->mg_conn.status_code = atoi(status);
3779+ }
3780+ conn->ns_conn->flags &= ~NSF_BUFFER_BUT_DONT_SEND;
3781+ }
3782+}
3783+#endif // !MONGOOSE_NO_CGI
3784+
3785+static char *mg_strdup(const char *str) {
3786+ char *copy = (char *) malloc(strlen(str) + 1);
3787+ if (copy != NULL) {
3788+ strcpy(copy, str);
3789+ }
3790+ return copy;
3791+}
3792+
3793+static int isbyte(int n) {
3794+ return n >= 0 && n <= 255;
3795+}
3796+
3797+static int parse_net(const char *spec, uint32_t *net, uint32_t *mask) {
3798+ int n, a, b, c, d, slash = 32, len = 0;
3799+
3800+ if ((sscanf(spec, "%d.%d.%d.%d/%d%n", &a, &b, &c, &d, &slash, &n) == 5 ||
3801+ sscanf(spec, "%d.%d.%d.%d%n", &a, &b, &c, &d, &n) == 4) &&
3802+ isbyte(a) && isbyte(b) && isbyte(c) && isbyte(d) &&
3803+ slash >= 0 && slash < 33) {
3804+ len = n;
3805+ *net = ((uint32_t)a << 24) | ((uint32_t)b << 16) | ((uint32_t)c << 8) | d;
3806+ *mask = slash ? 0xffffffffU << (32 - slash) : 0;
3807+ }
3808+
3809+ return len;
3810+}
3811+
3812+// Verify given socket address against the ACL.
3813+// Return -1 if ACL is malformed, 0 if address is disallowed, 1 if allowed.
3814+static int check_acl(const char *acl, uint32_t remote_ip) {
3815+ int allowed, flag;
3816+ uint32_t net, mask;
3817+ struct vec vec;
3818+
3819+ // If any ACL is set, deny by default
3820+ allowed = acl == NULL ? '+' : '-';
3821+
3822+ while ((acl = next_option(acl, &vec, NULL)) != NULL) {
3823+ flag = vec.ptr[0];
3824+ if ((flag != '+' && flag != '-') ||
3825+ parse_net(&vec.ptr[1], &net, &mask) == 0) {
3826+ return -1;
3827+ }
3828+
3829+ if (net == (remote_ip & mask)) {
3830+ allowed = flag;
3831+ }
3832+ }
3833+
3834+ return allowed == '+';
3835+}
3836+
3837+// Protect against directory disclosure attack by removing '..',
3838+// excessive '/' and '\' characters
3839+static void remove_double_dots_and_double_slashes(char *s) {
3840+ char *p = s;
3841+
3842+ while (*s != '\0') {
3843+ *p++ = *s++;
3844+ if (s[-1] == '/' || s[-1] == '\\') {
3845+ // Skip all following slashes, backslashes and double-dots
3846+ while (s[0] != '\0') {
3847+ if (s[0] == '/' || s[0] == '\\') { s++; }
3848+ else if (s[0] == '.' && s[1] == '.') { s += 2; }
3849+ else { break; }
3850+ }
3851+ }
3852+ }
3853+ *p = '\0';
3854+}
3855+
3856+int mg_url_decode(const char *src, int src_len, char *dst,
3857+ int dst_len, int is_form_url_encoded) {
3858+ int i, j, a, b;
3859+#define HEXTOI(x) (isdigit(x) ? x - '0' : x - 'W')
3860+
3861+ for (i = j = 0; i < src_len && j < dst_len - 1; i++, j++) {
3862+ if (src[i] == '%' && i < src_len - 2 &&
3863+ isxdigit(* (const unsigned char *) (src + i + 1)) &&
3864+ isxdigit(* (const unsigned char *) (src + i + 2))) {
3865+ a = tolower(* (const unsigned char *) (src + i + 1));
3866+ b = tolower(* (const unsigned char *) (src + i + 2));
3867+ dst[j] = (char) ((HEXTOI(a) << 4) | HEXTOI(b));
3868+ i += 2;
3869+ } else if (is_form_url_encoded && src[i] == '+') {
3870+ dst[j] = ' ';
3871+ } else {
3872+ dst[j] = src[i];
3873+ }
3874+ }
3875+
3876+ dst[j] = '\0'; // Null-terminate the destination
3877+
3878+ return i >= src_len ? j : -1;
3879+}
3880+
3881+static int is_valid_http_method(const char *s) {
3882+ return !strcmp(s, "GET") || !strcmp(s, "POST") || !strcmp(s, "HEAD") ||
3883+ !strcmp(s, "CONNECT") || !strcmp(s, "PUT") || !strcmp(s, "DELETE") ||
3884+ !strcmp(s, "OPTIONS") || !strcmp(s, "PROPFIND") || !strcmp(s, "MKCOL");
3885+}
3886+
3887+// Parse HTTP request, fill in mg_request structure.
3888+// This function modifies the buffer by NUL-terminating
3889+// HTTP request components, header names and header values.
3890+// Note that len must point to the last \n of HTTP headers.
3891+static int parse_http_message(char *buf, int len, struct mg_connection *ri) {
3892+ int is_request, n;
3893+
3894+ // Reset the connection. Make sure that we don't touch fields that are
3895+ // set elsewhere: remote_ip, remote_port, server_param
3896+ ri->request_method = ri->uri = ri->http_version = ri->query_string = NULL;
3897+ ri->num_headers = ri->status_code = ri->is_websocket = ri->content_len = 0;
3898+
3899+ buf[len - 1] = '\0';
3900+
3901+ // RFC says that all initial whitespaces should be ingored
3902+ while (*buf != '\0' && isspace(* (unsigned char *) buf)) {
3903+ buf++;
3904+ }
3905+ ri->request_method = skip(&buf, " ");
3906+ ri->uri = skip(&buf, " ");
3907+ ri->http_version = skip(&buf, "\r\n");
3908+
3909+ // HTTP message could be either HTTP request or HTTP response, e.g.
3910+ // "GET / HTTP/1.0 ...." or "HTTP/1.0 200 OK ..."
3911+ is_request = is_valid_http_method(ri->request_method);
3912+ if ((is_request && memcmp(ri->http_version, "HTTP/", 5) != 0) ||
3913+ (!is_request && memcmp(ri->request_method, "HTTP/", 5) != 0)) {
3914+ len = -1;
3915+ } else {
3916+ if (is_request) {
3917+ ri->http_version += 5;
3918+ } else {
3919+ ri->status_code = atoi(ri->uri);
3920+ }
3921+ parse_http_headers(&buf, ri);
3922+
3923+ if ((ri->query_string = strchr(ri->uri, '?')) != NULL) {
3924+ *(char *) ri->query_string++ = '\0';
3925+ }
3926+ n = (int) strlen(ri->uri);
3927+ mg_url_decode(ri->uri, n, (char *) ri->uri, n + 1, 0);
3928+ if (*ri->uri == '/' || *ri->uri == '.') {
3929+ remove_double_dots_and_double_slashes((char *) ri->uri);
3930+ }
3931+ }
3932+
3933+ return len;
3934+}
3935+
3936+static int lowercase(const char *s) {
3937+ return tolower(* (const unsigned char *) s);
3938+}
3939+
3940+static int mg_strcasecmp(const char *s1, const char *s2) {
3941+ int diff;
3942+
3943+ do {
3944+ diff = lowercase(s1++) - lowercase(s2++);
3945+ } while (diff == 0 && s1[-1] != '\0');
3946+
3947+ return diff;
3948+}
3949+
3950+static int mg_strncasecmp(const char *s1, const char *s2, size_t len) {
3951+ int diff = 0;
3952+
3953+ if (len > 0)
3954+ do {
3955+ diff = lowercase(s1++) - lowercase(s2++);
3956+ } while (diff == 0 && s1[-1] != '\0' && --len > 0);
3957+
3958+ return diff;
3959+}
3960+
3961+// Return HTTP header value, or NULL if not found.
3962+const char *mg_get_header(const struct mg_connection *ri, const char *s) {
3963+ int i;
3964+
3965+ for (i = 0; i < ri->num_headers; i++)
3966+ if (!mg_strcasecmp(s, ri->http_headers[i].name))
3967+ return ri->http_headers[i].value;
3968+
3969+ return NULL;
3970+}
3971+
3972+// Perform case-insensitive match of string against pattern
3973+int mg_match_prefix(const char *pattern, int pattern_len, const char *str) {
3974+ const char *or_str;
3975+ int len, res, i = 0, j = 0;
3976+
3977+ if ((or_str = (const char *) memchr(pattern, '|', pattern_len)) != NULL) {
3978+ res = mg_match_prefix(pattern, or_str - pattern, str);
3979+ return res > 0 ? res : mg_match_prefix(or_str + 1,
3980+ (pattern + pattern_len) - (or_str + 1), str);
3981+ }
3982+
3983+ for (; i < pattern_len; i++, j++) {
3984+ if (pattern[i] == '?' && str[j] != '\0') {
3985+ continue;
3986+ } else if (pattern[i] == '$') {
3987+ return str[j] == '\0' ? j : -1;
3988+ } else if (pattern[i] == '*') {
3989+ i++;
3990+ if (pattern[i] == '*') {
3991+ i++;
3992+ len = (int) strlen(str + j);
3993+ } else {
3994+ len = (int) strcspn(str + j, "/");
3995+ }
3996+ if (i == pattern_len) {
3997+ return j + len;
3998+ }
3999+ do {
4000+ res = mg_match_prefix(pattern + i, pattern_len - i, str + j + len);
4001+ } while (res == -1 && len-- > 0);
4002+ return res == -1 ? -1 : j + res + len;
4003+ } else if (lowercase(&pattern[i]) != lowercase(&str[j])) {
4004+ return -1;
4005+ }
4006+ }
4007+ return j;
4008+}
4009+
4010+// This function prints HTML pages, and expands "{{something}}" blocks
4011+// inside HTML by calling appropriate callback functions.
4012+// Note that {{@path/to/file}} construct outputs embedded file's contents,
4013+// which provides SSI-like functionality.
4014+void mg_template(struct mg_connection *conn, const char *s,
4015+ struct mg_expansion *expansions) {
4016+ int i, j, pos = 0, inside_marker = 0;
4017+
4018+ for (i = 0; s[i] != '\0'; i++) {
4019+ if (inside_marker == 0 && !memcmp(&s[i], "{{", 2)) {
4020+ if (i > pos) {
4021+ mg_send_data(conn, &s[pos], i - pos);
4022+ }
4023+ pos = i;
4024+ inside_marker = 1;
4025+ }
4026+ if (inside_marker == 1 && !memcmp(&s[i], "}}", 2)) {
4027+ for (j = 0; expansions[j].keyword != NULL; j++) {
4028+ const char *kw = expansions[j].keyword;
4029+ if ((int) strlen(kw) == i - (pos + 2) &&
4030+ memcmp(kw, &s[pos + 2], i - (pos + 2)) == 0) {
4031+ expansions[j].handler(conn);
4032+ pos = i + 2;
4033+ break;
4034+ }
4035+ }
4036+ inside_marker = 0;
4037+ }
4038+ }
4039+ if (i > pos) {
4040+ mg_send_data(conn, &s[pos], i - pos);
4041+ }
4042+}
4043+
4044+#ifndef MONGOOSE_NO_FILESYSTEM
4045+static int must_hide_file(struct connection *conn, const char *path) {
4046+ const char *pw_pattern = "**" PASSWORDS_FILE_NAME "$";
4047+ const char *pattern = conn->server->config_options[HIDE_FILES_PATTERN];
4048+ return mg_match_prefix(pw_pattern, strlen(pw_pattern), path) > 0 ||
4049+ (pattern != NULL && mg_match_prefix(pattern, strlen(pattern), path) > 0);
4050+}
4051+
4052+// Return 1 if real file has been found, 0 otherwise
4053+static int convert_uri_to_file_name(struct connection *conn, char *buf,
4054+ size_t buf_len, file_stat_t *st) {
4055+ struct vec a, b;
4056+ const char *rewrites = conn->server->config_options[URL_REWRITES];
4057+ const char *root = conn->server->config_options[DOCUMENT_ROOT];
4058+#ifndef MONGOOSE_NO_CGI
4059+ const char *cgi_pat = conn->server->config_options[CGI_PATTERN];
4060+ char *p;
4061+#endif
4062+ const char *uri = conn->mg_conn.uri;
4063+ const char *domain = mg_get_header(&conn->mg_conn, "Host");
4064+ int match_len, root_len = root == NULL ? 0 : strlen(root);
4065+
4066+ // Perform virtual hosting rewrites
4067+ if (rewrites != NULL && domain != NULL) {
4068+ const char *colon = strchr(domain, ':');
4069+ int domain_len = colon == NULL ? (int) strlen(domain) : colon - domain;
4070+
4071+ while ((rewrites = next_option(rewrites, &a, &b)) != NULL) {
4072+ if (a.len > 1 && a.ptr[0] == '@' && a.len == domain_len + 1 &&
4073+ mg_strncasecmp(a.ptr + 1, domain, domain_len) == 0) {
4074+ root = b.ptr;
4075+ root_len = b.len;
4076+ break;
4077+ }
4078+ }
4079+ }
4080+
4081+ // No filesystem access
4082+ if (root == NULL || root_len == 0) return 0;
4083+
4084+ // Handle URL rewrites
4085+ mg_snprintf(buf, buf_len, "%.*s%s", root_len, root, uri);
4086+ rewrites = conn->server->config_options[URL_REWRITES]; // Re-initialize!
4087+ while ((rewrites = next_option(rewrites, &a, &b)) != NULL) {
4088+ if ((match_len = mg_match_prefix(a.ptr, a.len, uri)) > 0) {
4089+ mg_snprintf(buf, buf_len, "%.*s%s", (int) b.len, b.ptr, uri + match_len);
4090+ break;
4091+ }
4092+ }
4093+
4094+ if (stat(buf, st) == 0) return 1;
4095+
4096+#ifndef MONGOOSE_NO_CGI
4097+ // Support PATH_INFO for CGI scripts.
4098+ for (p = buf + strlen(root) + 2; *p != '\0'; p++) {
4099+ if (*p == '/') {
4100+ *p = '\0';
4101+ if (mg_match_prefix(cgi_pat, strlen(cgi_pat), buf) > 0 &&
4102+ !stat(buf, st)) {
4103+ DBG(("!!!! [%s]", buf));
4104+ *p = '/';
4105+ conn->path_info = mg_strdup(p);
4106+ *p = '\0';
4107+ return 1;
4108+ }
4109+ *p = '/';
4110+ }
4111+ }
4112+#endif
4113+
4114+ return 0;
4115+}
4116+#endif // MONGOOSE_NO_FILESYSTEM
4117+
4118+static int should_keep_alive(const struct mg_connection *conn) {
4119+ struct connection *c = MG_CONN_2_CONN(conn);
4120+ const char *method = conn->request_method;
4121+ const char *http_version = conn->http_version;
4122+ const char *header = mg_get_header(conn, "Connection");
4123+ return method != NULL &&
4124+ (!strcmp(method, "GET") || c->endpoint_type == EP_USER) &&
4125+ ((header != NULL && !mg_strcasecmp(header, "keep-alive")) ||
4126+ (header == NULL && http_version && !strcmp(http_version, "1.1")));
4127+}
4128+
4129+size_t mg_write(struct mg_connection *c, const void *buf, int len) {
4130+ struct connection *conn = MG_CONN_2_CONN(c);
4131+ ns_send(conn->ns_conn, buf, len);
4132+ return conn->ns_conn->send_iobuf.len;
4133+}
4134+
4135+void mg_send_status(struct mg_connection *c, int status) {
4136+ if (c->status_code == 0) {
4137+ c->status_code = status;
4138+ mg_printf(c, "HTTP/1.1 %d %s\r\n", status, status_code_to_str(status));
4139+ }
4140+}
4141+
4142+void mg_send_header(struct mg_connection *c, const char *name, const char *v) {
4143+ if (c->status_code == 0) {
4144+ c->status_code = 200;
4145+ mg_printf(c, "HTTP/1.1 %d %s\r\n", 200, status_code_to_str(200));
4146+ }
4147+ mg_printf(c, "%s: %s\r\n", name, v);
4148+}
4149+
4150+static void terminate_headers(struct mg_connection *c) {
4151+ struct connection *conn = MG_CONN_2_CONN(c);
4152+ if (!(conn->ns_conn->flags & MG_HEADERS_SENT)) {
4153+ mg_send_header(c, "Transfer-Encoding", "chunked");
4154+ mg_write(c, "\r\n", 2);
4155+ conn->ns_conn->flags |= MG_HEADERS_SENT;
4156+ }
4157+}
4158+
4159+size_t mg_send_data(struct mg_connection *c, const void *data, int data_len) {
4160+ struct connection *conn = MG_CONN_2_CONN(c);
4161+ terminate_headers(c);
4162+ write_chunk(MG_CONN_2_CONN(c), (const char *) data, data_len);
4163+ return conn->ns_conn->send_iobuf.len;
4164+}
4165+
4166+size_t mg_printf_data(struct mg_connection *c, const char *fmt, ...) {
4167+ struct connection *conn = MG_CONN_2_CONN(c);
4168+ va_list ap;
4169+ int len;
4170+ char mem[IOBUF_SIZE], *buf = mem;
4171+
4172+ terminate_headers(c);
4173+
4174+ va_start(ap, fmt);
4175+ len = ns_avprintf(&buf, sizeof(mem), fmt, ap);
4176+ va_end(ap);
4177+
4178+ if (len >= 0) {
4179+ write_chunk((struct connection *) conn, buf, len);
4180+ }
4181+ if (buf != mem && buf != NULL) {
4182+ free(buf);
4183+ }
4184+ return conn->ns_conn->send_iobuf.len;
4185+}
4186+
4187+#if !defined(MONGOOSE_NO_WEBSOCKET) || !defined(MONGOOSE_NO_AUTH)
4188+static int is_big_endian(void) {
4189+ static const int n = 1;
4190+ return ((char *) &n)[0] == 0;
4191+}
4192+#endif
4193+
4194+#ifndef MONGOOSE_NO_WEBSOCKET
4195+// START OF SHA-1 code
4196+// Copyright(c) By Steve Reid <steve@edmweb.com>
4197+#define SHA1HANDSOFF
4198+#if defined(__sun)
4199+#include "solarisfixes.h"
4200+#endif
4201+
4202+union char64long16 { unsigned char c[64]; uint32_t l[16]; };
4203+
4204+#define rol(value, bits) (((value) << (bits)) | ((value) >> (32 - (bits))))
4205+
4206+static uint32_t blk0(union char64long16 *block, int i) {
4207+ // Forrest: SHA expect BIG_ENDIAN, swap if LITTLE_ENDIAN
4208+ if (!is_big_endian()) {
4209+ block->l[i] = (rol(block->l[i], 24) & 0xFF00FF00) |
4210+ (rol(block->l[i], 8) & 0x00FF00FF);
4211+ }
4212+ return block->l[i];
4213+}
4214+
4215+/* Avoid redefine warning (ARM /usr/include/sys/ucontext.h define R0~R4) */
4216+#undef blk
4217+#undef R0
4218+#undef R1
4219+#undef R2
4220+#undef R3
4221+#undef R4
4222+
4223+#define blk(i) (block->l[i&15] = rol(block->l[(i+13)&15]^block->l[(i+8)&15] \
4224+ ^block->l[(i+2)&15]^block->l[i&15],1))
4225+#define R0(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk0(block, i)+0x5A827999+rol(v,5);w=rol(w,30);
4226+#define R1(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk(i)+0x5A827999+rol(v,5);w=rol(w,30);
4227+#define R2(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0x6ED9EBA1+rol(v,5);w=rol(w,30);
4228+#define R3(v,w,x,y,z,i) z+=(((w|x)&y)|(w&x))+blk(i)+0x8F1BBCDC+rol(v,5);w=rol(w,30);
4229+#define R4(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0xCA62C1D6+rol(v,5);w=rol(w,30);
4230+
4231+typedef struct {
4232+ uint32_t state[5];
4233+ uint32_t count[2];
4234+ unsigned char buffer[64];
4235+} SHA1_CTX;
4236+
4237+static void SHA1Transform(uint32_t state[5], const unsigned char buffer[64]) {
4238+ uint32_t a, b, c, d, e;
4239+ union char64long16 block[1];
4240+
4241+ memcpy(block, buffer, 64);
4242+ a = state[0];
4243+ b = state[1];
4244+ c = state[2];
4245+ d = state[3];
4246+ e = state[4];
4247+ R0(a,b,c,d,e, 0); R0(e,a,b,c,d, 1); R0(d,e,a,b,c, 2); R0(c,d,e,a,b, 3);
4248+ R0(b,c,d,e,a, 4); R0(a,b,c,d,e, 5); R0(e,a,b,c,d, 6); R0(d,e,a,b,c, 7);
4249+ R0(c,d,e,a,b, 8); R0(b,c,d,e,a, 9); R0(a,b,c,d,e,10); R0(e,a,b,c,d,11);
4250+ R0(d,e,a,b,c,12); R0(c,d,e,a,b,13); R0(b,c,d,e,a,14); R0(a,b,c,d,e,15);
4251+ R1(e,a,b,c,d,16); R1(d,e,a,b,c,17); R1(c,d,e,a,b,18); R1(b,c,d,e,a,19);
4252+ R2(a,b,c,d,e,20); R2(e,a,b,c,d,21); R2(d,e,a,b,c,22); R2(c,d,e,a,b,23);
4253+ R2(b,c,d,e,a,24); R2(a,b,c,d,e,25); R2(e,a,b,c,d,26); R2(d,e,a,b,c,27);
4254+ R2(c,d,e,a,b,28); R2(b,c,d,e,a,29); R2(a,b,c,d,e,30); R2(e,a,b,c,d,31);
4255+ R2(d,e,a,b,c,32); R2(c,d,e,a,b,33); R2(b,c,d,e,a,34); R2(a,b,c,d,e,35);
4256+ R2(e,a,b,c,d,36); R2(d,e,a,b,c,37); R2(c,d,e,a,b,38); R2(b,c,d,e,a,39);
4257+ R3(a,b,c,d,e,40); R3(e,a,b,c,d,41); R3(d,e,a,b,c,42); R3(c,d,e,a,b,43);
4258+ R3(b,c,d,e,a,44); R3(a,b,c,d,e,45); R3(e,a,b,c,d,46); R3(d,e,a,b,c,47);
4259+ R3(c,d,e,a,b,48); R3(b,c,d,e,a,49); R3(a,b,c,d,e,50); R3(e,a,b,c,d,51);
4260+ R3(d,e,a,b,c,52); R3(c,d,e,a,b,53); R3(b,c,d,e,a,54); R3(a,b,c,d,e,55);
4261+ R3(e,a,b,c,d,56); R3(d,e,a,b,c,57); R3(c,d,e,a,b,58); R3(b,c,d,e,a,59);
4262+ R4(a,b,c,d,e,60); R4(e,a,b,c,d,61); R4(d,e,a,b,c,62); R4(c,d,e,a,b,63);
4263+ R4(b,c,d,e,a,64); R4(a,b,c,d,e,65); R4(e,a,b,c,d,66); R4(d,e,a,b,c,67);
4264+ R4(c,d,e,a,b,68); R4(b,c,d,e,a,69); R4(a,b,c,d,e,70); R4(e,a,b,c,d,71);
4265+ R4(d,e,a,b,c,72); R4(c,d,e,a,b,73); R4(b,c,d,e,a,74); R4(a,b,c,d,e,75);
4266+ R4(e,a,b,c,d,76); R4(d,e,a,b,c,77); R4(c,d,e,a,b,78); R4(b,c,d,e,a,79);
4267+ state[0] += a;
4268+ state[1] += b;
4269+ state[2] += c;
4270+ state[3] += d;
4271+ state[4] += e;
4272+ // Erase working structures. The order of operations is important,
4273+ // used to ensure that compiler doesn't optimize those out.
4274+ memset(block, 0, sizeof(block));
4275+ a = b = c = d = e = 0;
4276+ (void) a; (void) b; (void) c; (void) d; (void) e;
4277+}
4278+
4279+static void SHA1Init(SHA1_CTX *context) {
4280+ context->state[0] = 0x67452301;
4281+ context->state[1] = 0xEFCDAB89;
4282+ context->state[2] = 0x98BADCFE;
4283+ context->state[3] = 0x10325476;
4284+ context->state[4] = 0xC3D2E1F0;
4285+ context->count[0] = context->count[1] = 0;
4286+}
4287+
4288+static void SHA1Update(SHA1_CTX *context, const unsigned char *data,
4289+ uint32_t len) {
4290+ uint32_t i, j;
4291+
4292+ j = context->count[0];
4293+ if ((context->count[0] += len << 3) < j)
4294+ context->count[1]++;
4295+ context->count[1] += (len>>29);
4296+ j = (j >> 3) & 63;
4297+ if ((j + len) > 63) {
4298+ memcpy(&context->buffer[j], data, (i = 64-j));
4299+ SHA1Transform(context->state, context->buffer);
4300+ for ( ; i + 63 < len; i += 64) {
4301+ SHA1Transform(context->state, &data[i]);
4302+ }
4303+ j = 0;
4304+ }
4305+ else i = 0;
4306+ memcpy(&context->buffer[j], &data[i], len - i);
4307+}
4308+
4309+static void SHA1Final(unsigned char digest[20], SHA1_CTX *context) {
4310+ unsigned i;
4311+ unsigned char finalcount[8], c;
4312+
4313+ for (i = 0; i < 8; i++) {
4314+ finalcount[i] = (unsigned char)((context->count[(i >= 4 ? 0 : 1)]
4315+ >> ((3-(i & 3)) * 8) ) & 255);
4316+ }
4317+ c = 0200;
4318+ SHA1Update(context, &c, 1);
4319+ while ((context->count[0] & 504) != 448) {
4320+ c = 0000;
4321+ SHA1Update(context, &c, 1);
4322+ }
4323+ SHA1Update(context, finalcount, 8);
4324+ for (i = 0; i < 20; i++) {
4325+ digest[i] = (unsigned char)
4326+ ((context->state[i>>2] >> ((3-(i & 3)) * 8) ) & 255);
4327+ }
4328+ memset(context, '\0', sizeof(*context));
4329+ memset(&finalcount, '\0', sizeof(finalcount));
4330+}
4331+// END OF SHA1 CODE
4332+
4333+static void base64_encode(const unsigned char *src, int src_len, char *dst) {
4334+ static const char *b64 =
4335+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
4336+ int i, j, a, b, c;
4337+
4338+ for (i = j = 0; i < src_len; i += 3) {
4339+ a = src[i];
4340+ b = i + 1 >= src_len ? 0 : src[i + 1];
4341+ c = i + 2 >= src_len ? 0 : src[i + 2];
4342+
4343+ dst[j++] = b64[a >> 2];
4344+ dst[j++] = b64[((a & 3) << 4) | (b >> 4)];
4345+ if (i + 1 < src_len) {
4346+ dst[j++] = b64[(b & 15) << 2 | (c >> 6)];
4347+ }
4348+ if (i + 2 < src_len) {
4349+ dst[j++] = b64[c & 63];
4350+ }
4351+ }
4352+ while (j % 4 != 0) {
4353+ dst[j++] = '=';
4354+ }
4355+ dst[j++] = '\0';
4356+}
4357+
4358+static void send_websocket_handshake(struct mg_connection *conn,
4359+ const char *key) {
4360+ static const char *magic = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
4361+ char buf[500], sha[20], b64_sha[sizeof(sha) * 2];
4362+ SHA1_CTX sha_ctx;
4363+
4364+ mg_snprintf(buf, sizeof(buf), "%s%s", key, magic);
4365+ SHA1Init(&sha_ctx);
4366+ SHA1Update(&sha_ctx, (unsigned char *) buf, strlen(buf));
4367+ SHA1Final((unsigned char *) sha, &sha_ctx);
4368+ base64_encode((unsigned char *) sha, sizeof(sha), b64_sha);
4369+ mg_snprintf(buf, sizeof(buf), "%s%s%s",
4370+ "HTTP/1.1 101 Switching Protocols\r\n"
4371+ "Upgrade: websocket\r\n"
4372+ "Connection: Upgrade\r\n"
4373+ "Sec-WebSocket-Accept: ", b64_sha, "\r\n\r\n");
4374+
4375+ mg_write(conn, buf, strlen(buf));
4376+}
4377+
4378+static int deliver_websocket_frame(struct connection *conn) {
4379+ // Having buf unsigned char * is important, as it is used below in arithmetic
4380+ unsigned char *buf = (unsigned char *) conn->ns_conn->recv_iobuf.buf;
4381+ int i, len, buf_len = conn->ns_conn->recv_iobuf.len, frame_len = 0,
4382+ mask_len = 0, header_len = 0, data_len = 0, buffered = 0;
4383+
4384+ if (buf_len >= 2) {
4385+ len = buf[1] & 127;
4386+ mask_len = buf[1] & 128 ? 4 : 0;
4387+ if (len < 126 && buf_len >= mask_len) {
4388+ data_len = len;
4389+ header_len = 2 + mask_len;
4390+ } else if (len == 126 && buf_len >= 4 + mask_len) {
4391+ header_len = 4 + mask_len;
4392+ data_len = ((((int) buf[2]) << 8) + buf[3]);
4393+ } else if (buf_len >= 10 + mask_len) {
4394+ header_len = 10 + mask_len;
4395+ data_len = (int) (((uint64_t) htonl(* (uint32_t *) &buf[2])) << 32) +
4396+ htonl(* (uint32_t *) &buf[6]);
4397+ }
4398+ }
4399+
4400+ frame_len = header_len + data_len;
4401+ buffered = frame_len > 0 && frame_len <= buf_len;
4402+
4403+ if (buffered) {
4404+ conn->mg_conn.content_len = data_len;
4405+ conn->mg_conn.content = (char *) buf + header_len;
4406+ conn->mg_conn.wsbits = buf[0];
4407+
4408+ // Apply mask if necessary
4409+ if (mask_len > 0) {
4410+ for (i = 0; i < data_len; i++) {
4411+ buf[i + header_len] ^= (buf + header_len - mask_len)[i % 4];
4412+ }
4413+ }
4414+
4415+ // Call the handler and remove frame from the iobuf
4416+ if (call_user(conn, MG_REQUEST) == MG_FALSE) {
4417+ conn->ns_conn->flags |= NSF_FINISHED_SENDING_DATA;
4418+ }
4419+ iobuf_remove(&conn->ns_conn->recv_iobuf, frame_len);
4420+ }
4421+
4422+ return buffered;
4423+}
4424+
4425+size_t mg_websocket_write(struct mg_connection *conn, int opcode,
4426+ const char *data, size_t data_len) {
4427+ unsigned char mem[4192], *copy = mem;
4428+ size_t copy_len = 0;
4429+
4430+ if (data_len + 10 > sizeof(mem) &&
4431+ (copy = (unsigned char *) malloc(data_len + 10)) == NULL) {
4432+ return 0;
4433+ }
4434+
4435+ copy[0] = 0x80 + (opcode & 0x0f);
4436+
4437+ // Frame format: http://tools.ietf.org/html/rfc6455#section-5.2
4438+ if (data_len < 126) {
4439+ // Inline 7-bit length field
4440+ copy[1] = data_len;
4441+ memcpy(copy + 2, data, data_len);
4442+ copy_len = 2 + data_len;
4443+ } else if (data_len <= 0xFFFF) {
4444+ // 16-bit length field
4445+ copy[1] = 126;
4446+ * (uint16_t *) (copy + 2) = (uint16_t) htons((uint16_t) data_len);
4447+ memcpy(copy + 4, data, data_len);
4448+ copy_len = 4 + data_len;
4449+ } else {
4450+ // 64-bit length field
4451+ copy[1] = 127;
4452+ * (uint32_t *) (copy + 2) = (uint32_t)
4453+ htonl((uint32_t) ((uint64_t) data_len >> 32));
4454+ * (uint32_t *) (copy + 6) = (uint32_t) htonl(data_len & 0xffffffff);
4455+ memcpy(copy + 10, data, data_len);
4456+ copy_len = 10 + data_len;
4457+ }
4458+
4459+ if (copy_len > 0) {
4460+ mg_write(conn, copy, copy_len);
4461+ }
4462+ if (copy != mem) {
4463+ free(copy);
4464+ }
4465+
4466+ // If we send closing frame, schedule a connection to be closed after
4467+ // data is drained to the client.
4468+ if (opcode == WEBSOCKET_OPCODE_CONNECTION_CLOSE) {
4469+ MG_CONN_2_CONN(conn)->ns_conn->flags |= NSF_FINISHED_SENDING_DATA;
4470+ }
4471+
4472+ return MG_CONN_2_CONN(conn)->ns_conn->send_iobuf.len;
4473+}
4474+
4475+size_t mg_websocket_printf(struct mg_connection *conn, int opcode,
4476+ const char *fmt, ...) {
4477+ char mem[4192], *buf = mem;
4478+ va_list ap;
4479+ int len;
4480+
4481+ va_start(ap, fmt);
4482+ if ((len = ns_avprintf(&buf, sizeof(mem), fmt, ap)) > 0) {
4483+ mg_websocket_write(conn, opcode, buf, len);
4484+ }
4485+ va_end(ap);
4486+
4487+ if (buf != mem && buf != NULL) {
4488+ free(buf);
4489+ }
4490+
4491+ return MG_CONN_2_CONN(conn)->ns_conn->send_iobuf.len;
4492+}
4493+
4494+static void send_websocket_handshake_if_requested(struct mg_connection *conn) {
4495+ const char *ver = mg_get_header(conn, "Sec-WebSocket-Version"),
4496+ *key = mg_get_header(conn, "Sec-WebSocket-Key");
4497+ if (ver != NULL && key != NULL) {
4498+ conn->is_websocket = 1;
4499+ if (call_user(MG_CONN_2_CONN(conn), MG_WS_HANDSHAKE) == MG_FALSE) {
4500+ send_websocket_handshake(conn, key);
4501+ }
4502+ call_user(MG_CONN_2_CONN(conn), MG_WS_CONNECT);
4503+ }
4504+}
4505+
4506+static void ping_idle_websocket_connection(struct connection *conn, time_t t) {
4507+ if (t - conn->ns_conn->last_io_time > MONGOOSE_USE_WEBSOCKET_PING_INTERVAL) {
4508+ mg_websocket_write(&conn->mg_conn, WEBSOCKET_OPCODE_PING, "", 0);
4509+ }
4510+}
4511+#else
4512+#define ping_idle_websocket_connection(conn, t)
4513+#endif // !MONGOOSE_NO_WEBSOCKET
4514+
4515+static void write_terminating_chunk(struct connection *conn) {
4516+ mg_write(&conn->mg_conn, "0\r\n\r\n", 5);
4517+}
4518+
4519+static int call_request_handler(struct connection *conn) {
4520+ int result;
4521+ conn->mg_conn.content = conn->ns_conn->recv_iobuf.buf;
4522+ if ((result = call_user(conn, MG_REQUEST)) == MG_TRUE) {
4523+ if (conn->ns_conn->flags & MG_HEADERS_SENT) {
4524+ write_terminating_chunk(conn);
4525+ }
4526+ close_local_endpoint(conn);
4527+ }
4528+ return result;
4529+}
4530+
4531+const char *mg_get_mime_type(const char *path, const char *default_mime_type) {
4532+ const char *ext;
4533+ size_t i, path_len;
4534+
4535+ path_len = strlen(path);
4536+
4537+ for (i = 0; static_builtin_mime_types[i].extension != NULL; i++) {
4538+ ext = path + (path_len - static_builtin_mime_types[i].ext_len);
4539+ if (path_len > static_builtin_mime_types[i].ext_len &&
4540+ mg_strcasecmp(ext, static_builtin_mime_types[i].extension) == 0) {
4541+ return static_builtin_mime_types[i].mime_type;
4542+ }
4543+ }
4544+
4545+ return default_mime_type;
4546+}
4547+
4548+#ifndef MONGOOSE_NO_FILESYSTEM
4549+// Convert month to the month number. Return -1 on error, or month number
4550+static int get_month_index(const char *s) {
4551+ static const char *month_names[] = {
4552+ "Jan", "Feb", "Mar", "Apr", "May", "Jun",
4553+ "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
4554+ };
4555+ int i;
4556+
4557+ for (i = 0; i < (int) ARRAY_SIZE(month_names); i++)
4558+ if (!strcmp(s, month_names[i]))
4559+ return i;
4560+
4561+ return -1;
4562+}
4563+
4564+static int num_leap_years(int year) {
4565+ return year / 4 - year / 100 + year / 400;
4566+}
4567+
4568+// Parse UTC date-time string, and return the corresponding time_t value.
4569+static time_t parse_date_string(const char *datetime) {
4570+ static const unsigned short days_before_month[] = {
4571+ 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334
4572+ };
4573+ char month_str[32];
4574+ int second, minute, hour, day, month, year, leap_days, days;
4575+ time_t result = (time_t) 0;
4576+
4577+ if (((sscanf(datetime, "%d/%3s/%d %d:%d:%d",
4578+ &day, month_str, &year, &hour, &minute, &second) == 6) ||
4579+ (sscanf(datetime, "%d %3s %d %d:%d:%d",
4580+ &day, month_str, &year, &hour, &minute, &second) == 6) ||
4581+ (sscanf(datetime, "%*3s, %d %3s %d %d:%d:%d",
4582+ &day, month_str, &year, &hour, &minute, &second) == 6) ||
4583+ (sscanf(datetime, "%d-%3s-%d %d:%d:%d",
4584+ &day, month_str, &year, &hour, &minute, &second) == 6)) &&
4585+ year > 1970 &&
4586+ (month = get_month_index(month_str)) != -1) {
4587+ leap_days = num_leap_years(year) - num_leap_years(1970);
4588+ year -= 1970;
4589+ days = year * 365 + days_before_month[month] + (day - 1) + leap_days;
4590+ result = days * 24 * 3600 + hour * 3600 + minute * 60 + second;
4591+ }
4592+
4593+ return result;
4594+}
4595+
4596+// Look at the "path" extension and figure what mime type it has.
4597+// Store mime type in the vector.
4598+static void get_mime_type(const struct mg_server *server, const char *path,
4599+ struct vec *vec) {
4600+ struct vec ext_vec, mime_vec;
4601+ const char *list, *ext;
4602+ size_t path_len;
4603+
4604+ path_len = strlen(path);
4605+
4606+ // Scan user-defined mime types first, in case user wants to
4607+ // override default mime types.
4608+ list = server->config_options[EXTRA_MIME_TYPES];
4609+ while ((list = next_option(list, &ext_vec, &mime_vec)) != NULL) {
4610+ // ext now points to the path suffix
4611+ ext = path + path_len - ext_vec.len;
4612+ if (mg_strncasecmp(ext, ext_vec.ptr, ext_vec.len) == 0) {
4613+ *vec = mime_vec;
4614+ return;
4615+ }
4616+ }
4617+
4618+ vec->ptr = mg_get_mime_type(path, "text/plain");
4619+ vec->len = strlen(vec->ptr);
4620+}
4621+
4622+static const char *suggest_connection_header(const struct mg_connection *conn) {
4623+ return should_keep_alive(conn) ? "keep-alive" : "close";
4624+}
4625+
4626+static void construct_etag(char *buf, size_t buf_len, const file_stat_t *st) {
4627+ mg_snprintf(buf, buf_len, "\"%lx.%" INT64_FMT "\"",
4628+ (unsigned long) st->st_mtime, (int64_t) st->st_size);
4629+}
4630+
4631+// Return True if we should reply 304 Not Modified.
4632+static int is_not_modified(const struct connection *conn,
4633+ const file_stat_t *stp) {
4634+ char etag[64];
4635+ const char *ims = mg_get_header(&conn->mg_conn, "If-Modified-Since");
4636+ const char *inm = mg_get_header(&conn->mg_conn, "If-None-Match");
4637+ construct_etag(etag, sizeof(etag), stp);
4638+ return (inm != NULL && !mg_strcasecmp(etag, inm)) ||
4639+ (ims != NULL && stp->st_mtime <= parse_date_string(ims));
4640+}
4641+
4642+// For given directory path, substitute it to valid index file.
4643+// Return 0 if index file has been found, -1 if not found.
4644+// If the file is found, it's stats is returned in stp.
4645+static int find_index_file(struct connection *conn, char *path,
4646+ size_t path_len, file_stat_t *stp) {
4647+ const char *list = conn->server->config_options[INDEX_FILES];
4648+ file_stat_t st;
4649+ struct vec filename_vec;
4650+ size_t n = strlen(path), found = 0;
4651+
4652+ // The 'path' given to us points to the directory. Remove all trailing
4653+ // directory separator characters from the end of the path, and
4654+ // then append single directory separator character.
4655+ while (n > 0 && path[n - 1] == '/') {
4656+ n--;
4657+ }
4658+ path[n] = '/';
4659+
4660+ // Traverse index files list. For each entry, append it to the given
4661+ // path and see if the file exists. If it exists, break the loop
4662+ while ((list = next_option(list, &filename_vec, NULL)) != NULL) {
4663+
4664+ // Ignore too long entries that may overflow path buffer
4665+ if (filename_vec.len > (int) (path_len - (n + 2)))
4666+ continue;
4667+
4668+ // Prepare full path to the index file
4669+ strncpy(path + n + 1, filename_vec.ptr, filename_vec.len);
4670+ path[n + 1 + filename_vec.len] = '\0';
4671+
4672+ //DBG(("[%s]", path));
4673+
4674+ // Does it exist?
4675+ if (!stat(path, &st)) {
4676+ // Yes it does, break the loop
4677+ *stp = st;
4678+ found = 1;
4679+ break;
4680+ }
4681+ }
4682+
4683+ // If no index file exists, restore directory path
4684+ if (!found) {
4685+ path[n] = '\0';
4686+ }
4687+
4688+ return found;
4689+}
4690+
4691+static int parse_range_header(const char *header, int64_t *a, int64_t *b) {
4692+ return sscanf(header, "bytes=%" INT64_FMT "-%" INT64_FMT, a, b);
4693+}
4694+
4695+static void gmt_time_string(char *buf, size_t buf_len, time_t *t) {
4696+ strftime(buf, buf_len, "%a, %d %b %Y %H:%M:%S GMT", gmtime(t));
4697+}
4698+
4699+static void open_file_endpoint(struct connection *conn, const char *path,
4700+ file_stat_t *st, const char *extra_headers) {
4701+ char date[64], lm[64], etag[64], range[64], headers[1000];
4702+ const char *msg = "OK", *hdr;
4703+ time_t curtime = time(NULL);
4704+ int64_t r1, r2;
4705+ struct vec mime_vec;
4706+ int n;
4707+
4708+ conn->endpoint_type = EP_FILE;
4709+ ns_set_close_on_exec(conn->endpoint.fd);
4710+ conn->mg_conn.status_code = 200;
4711+
4712+ get_mime_type(conn->server, path, &mime_vec);
4713+ conn->cl = st->st_size;
4714+ range[0] = '\0';
4715+
4716+ // If Range: header specified, act accordingly
4717+ r1 = r2 = 0;
4718+ hdr = mg_get_header(&conn->mg_conn, "Range");
4719+ if (hdr != NULL && (n = parse_range_header(hdr, &r1, &r2)) > 0 &&
4720+ r1 >= 0 && r2 >= 0) {
4721+ conn->mg_conn.status_code = 206;
4722+ conn->cl = n == 2 ? (r2 > conn->cl ? conn->cl : r2) - r1 + 1: conn->cl - r1;
4723+ mg_snprintf(range, sizeof(range), "Content-Range: bytes "
4724+ "%" INT64_FMT "-%" INT64_FMT "/%" INT64_FMT "\r\n",
4725+ r1, r1 + conn->cl - 1, (int64_t) st->st_size);
4726+ msg = "Partial Content";
4727+ lseek(conn->endpoint.fd, r1, SEEK_SET);
4728+ }
4729+
4730+ // Prepare Etag, Date, Last-Modified headers. Must be in UTC, according to
4731+ // http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3
4732+ gmt_time_string(date, sizeof(date), &curtime);
4733+ gmt_time_string(lm, sizeof(lm), &st->st_mtime);
4734+ construct_etag(etag, sizeof(etag), st);
4735+
4736+ n = mg_snprintf(headers, sizeof(headers),
4737+ "HTTP/1.1 %d %s\r\n"
4738+ "Date: %s\r\n"
4739+ "Last-Modified: %s\r\n"
4740+ "Etag: %s\r\n"
4741+ "Content-Type: %.*s\r\n"
4742+ "Content-Length: %" INT64_FMT "\r\n"
4743+ "Connection: %s\r\n"
4744+ "Accept-Ranges: bytes\r\n"
4745+ "%s%s%s\r\n",
4746+ conn->mg_conn.status_code, msg, date, lm, etag,
4747+ (int) mime_vec.len, mime_vec.ptr, conn->cl,
4748+ suggest_connection_header(&conn->mg_conn),
4749+ range, extra_headers == NULL ? "" : extra_headers,
4750+ MONGOOSE_USE_EXTRA_HTTP_HEADERS);
4751+ ns_send(conn->ns_conn, headers, n);
4752+
4753+ if (!strcmp(conn->mg_conn.request_method, "HEAD")) {
4754+ conn->ns_conn->flags |= NSF_FINISHED_SENDING_DATA;
4755+ close(conn->endpoint.fd);
4756+ conn->endpoint_type = EP_NONE;
4757+ }
4758+}
4759+
4760+void mg_send_file_data(struct mg_connection *c, int fd) {
4761+ struct connection *conn = MG_CONN_2_CONN(c);
4762+ conn->endpoint_type = EP_FILE;
4763+ conn->endpoint.fd = fd;
4764+ ns_set_close_on_exec(conn->endpoint.fd);
4765+}
4766+#endif // MONGOOSE_NO_FILESYSTEM
4767+
4768+static void call_request_handler_if_data_is_buffered(struct connection *conn) {
4769+#ifndef MONGOOSE_NO_WEBSOCKET
4770+ if (conn->mg_conn.is_websocket) {
4771+ do { } while (deliver_websocket_frame(conn));
4772+ } else
4773+#endif
4774+ if (conn->num_bytes_recv >= (conn->cl + conn->request_len) &&
4775+ call_request_handler(conn) == MG_FALSE) {
4776+ open_local_endpoint(conn, 1);
4777+ }
4778+}
4779+
4780+#if !defined(MONGOOSE_NO_DIRECTORY_LISTING) || !defined(MONGOOSE_NO_DAV)
4781+
4782+#ifdef _WIN32
4783+struct dirent {
4784+ char d_name[MAX_PATH_SIZE];
4785+};
4786+
4787+typedef struct DIR {
4788+ HANDLE handle;
4789+ WIN32_FIND_DATAW info;
4790+ struct dirent result;
4791+} DIR;
4792+
4793+// Implementation of POSIX opendir/closedir/readdir for Windows.
4794+static DIR *opendir(const char *name) {
4795+ DIR *dir = NULL;
4796+ wchar_t wpath[MAX_PATH_SIZE];
4797+ DWORD attrs;
4798+
4799+ if (name == NULL) {
4800+ SetLastError(ERROR_BAD_ARGUMENTS);
4801+ } else if ((dir = (DIR *) malloc(sizeof(*dir))) == NULL) {
4802+ SetLastError(ERROR_NOT_ENOUGH_MEMORY);
4803+ } else {
4804+ to_wchar(name, wpath, ARRAY_SIZE(wpath));
4805+ attrs = GetFileAttributesW(wpath);
4806+ if (attrs != 0xFFFFFFFF &&
4807+ ((attrs & FILE_ATTRIBUTE_DIRECTORY) == FILE_ATTRIBUTE_DIRECTORY)) {
4808+ (void) wcscat(wpath, L"\\*");
4809+ dir->handle = FindFirstFileW(wpath, &dir->info);
4810+ dir->result.d_name[0] = '\0';
4811+ } else {
4812+ free(dir);
4813+ dir = NULL;
4814+ }
4815+ }
4816+
4817+ return dir;
4818+}
4819+
4820+static int closedir(DIR *dir) {
4821+ int result = 0;
4822+
4823+ if (dir != NULL) {
4824+ if (dir->handle != INVALID_HANDLE_VALUE)
4825+ result = FindClose(dir->handle) ? 0 : -1;
4826+
4827+ free(dir);
4828+ } else {
4829+ result = -1;
4830+ SetLastError(ERROR_BAD_ARGUMENTS);
4831+ }
4832+
4833+ return result;
4834+}
4835+
4836+static struct dirent *readdir(DIR *dir) {
4837+ struct dirent *result = 0;
4838+
4839+ if (dir) {
4840+ if (dir->handle != INVALID_HANDLE_VALUE) {
4841+ result = &dir->result;
4842+ (void) WideCharToMultiByte(CP_UTF8, 0,
4843+ dir->info.cFileName, -1, result->d_name,
4844+ sizeof(result->d_name), NULL, NULL);
4845+
4846+ if (!FindNextFileW(dir->handle, &dir->info)) {
4847+ (void) FindClose(dir->handle);
4848+ dir->handle = INVALID_HANDLE_VALUE;
4849+ }
4850+
4851+ } else {
4852+ SetLastError(ERROR_FILE_NOT_FOUND);
4853+ }
4854+ } else {
4855+ SetLastError(ERROR_BAD_ARGUMENTS);
4856+ }
4857+
4858+ return result;
4859+}
4860+#endif // _WIN32 POSIX opendir/closedir/readdir implementation
4861+
4862+static int scan_directory(struct connection *conn, const char *dir,
4863+ struct dir_entry **arr) {
4864+ char path[MAX_PATH_SIZE];
4865+ struct dir_entry *p;
4866+ struct dirent *dp;
4867+ int arr_size = 0, arr_ind = 0, inc = 100;
4868+ DIR *dirp;
4869+
4870+ *arr = NULL;
4871+ if ((dirp = (opendir(dir))) == NULL) return 0;
4872+
4873+ while ((dp = readdir(dirp)) != NULL) {
4874+ // Do not show current dir and hidden files
4875+ if (!strcmp(dp->d_name, ".") ||
4876+ !strcmp(dp->d_name, "..") ||
4877+ must_hide_file(conn, dp->d_name)) {
4878+ continue;
4879+ }
4880+ mg_snprintf(path, sizeof(path), "%s%c%s", dir, '/', dp->d_name);
4881+
4882+ // Resize the array if nesessary
4883+ if (arr_ind >= arr_size) {
4884+ if ((p = (struct dir_entry *)
4885+ realloc(*arr, (inc + arr_size) * sizeof(**arr))) != NULL) {
4886+ // Memset new chunk to zero, otherwize st_mtime will have garbage which
4887+ // can make strftime() segfault, see
4888+ // http://code.google.com/p/mongoose/issues/detail?id=79
4889+ memset(p + arr_size, 0, sizeof(**arr) * inc);
4890+
4891+ *arr = p;
4892+ arr_size += inc;
4893+ }
4894+ }
4895+
4896+ if (arr_ind < arr_size) {
4897+ (*arr)[arr_ind].conn = conn;
4898+ (*arr)[arr_ind].file_name = strdup(dp->d_name);
4899+ stat(path, &(*arr)[arr_ind].st);
4900+ arr_ind++;
4901+ }
4902+ }
4903+ closedir(dirp);
4904+
4905+ return arr_ind;
4906+}
4907+
4908+int mg_url_encode(const char *src, size_t s_len, char *dst, size_t dst_len) {
4909+ static const char *dont_escape = "._-$,;~()";
4910+ static const char *hex = "0123456789abcdef";
4911+ size_t i = 0, j = 0;
4912+
4913+ for (i = j = 0; dst_len > 0 && i < s_len && j + 2 < dst_len - 1; i++, j++) {
4914+ if (isalnum(* (const unsigned char *) (src + i)) ||
4915+ strchr(dont_escape, * (const unsigned char *) (src + i)) != NULL) {
4916+ dst[j] = src[i];
4917+ } else if (j + 3 < dst_len) {
4918+ dst[j] = '%';
4919+ dst[j + 1] = hex[(* (const unsigned char *) (src + i)) >> 4];
4920+ dst[j + 2] = hex[(* (const unsigned char *) (src + i)) & 0xf];
4921+ j += 2;
4922+ }
4923+ }
4924+
4925+ dst[j] = '\0';
4926+ return j;
4927+}
4928+#endif // !NO_DIRECTORY_LISTING || !MONGOOSE_NO_DAV
4929+
4930+#ifndef MONGOOSE_NO_DIRECTORY_LISTING
4931+
4932+static void print_dir_entry(const struct dir_entry *de) {
4933+ char size[64], mod[64], href[MAX_PATH_SIZE * 3];
4934+ int64_t fsize = de->st.st_size;
4935+ int is_dir = S_ISDIR(de->st.st_mode);
4936+ const char *slash = is_dir ? "/" : "";
4937+
4938+ if (is_dir) {
4939+ mg_snprintf(size, sizeof(size), "%s", "[DIRECTORY]");
4940+ } else {
4941+ // We use (signed) cast below because MSVC 6 compiler cannot
4942+ // convert unsigned __int64 to double.
4943+ if (fsize < 1024) {
4944+ mg_snprintf(size, sizeof(size), "%d", (int) fsize);
4945+ } else if (fsize < 0x100000) {
4946+ mg_snprintf(size, sizeof(size), "%.1fk", (double) fsize / 1024.0);
4947+ } else if (fsize < 0x40000000) {
4948+ mg_snprintf(size, sizeof(size), "%.1fM", (double) fsize / 1048576);
4949+ } else {
4950+ mg_snprintf(size, sizeof(size), "%.1fG", (double) fsize / 1073741824);
4951+ }
4952+ }
4953+ strftime(mod, sizeof(mod), "%d-%b-%Y %H:%M", localtime(&de->st.st_mtime));
4954+ mg_url_encode(de->file_name, strlen(de->file_name), href, sizeof(href));
4955+ mg_printf_data(&de->conn->mg_conn,
4956+ "<tr><td><a href=\"%s%s\">%s%s</a></td>"
4957+ "<td>&nbsp;%s</td><td>&nbsp;&nbsp;%s</td></tr>\n",
4958+ href, slash, de->file_name, slash, mod, size);
4959+}
4960+
4961+// Sort directory entries by size, or name, or modification time.
4962+// On windows, __cdecl specification is needed in case if project is built
4963+// with __stdcall convention. qsort always requires __cdels callback.
4964+static int __cdecl compare_dir_entries(const void *p1, const void *p2) {
4965+ const struct dir_entry *a = (const struct dir_entry *) p1,
4966+ *b = (const struct dir_entry *) p2;
4967+ const char *qs = a->conn->mg_conn.query_string ?
4968+ a->conn->mg_conn.query_string : "na";
4969+ int cmp_result = 0;
4970+
4971+ if (S_ISDIR(a->st.st_mode) && !S_ISDIR(b->st.st_mode)) {
4972+ return -1; // Always put directories on top
4973+ } else if (!S_ISDIR(a->st.st_mode) && S_ISDIR(b->st.st_mode)) {
4974+ return 1; // Always put directories on top
4975+ } else if (*qs == 'n') {
4976+ cmp_result = strcmp(a->file_name, b->file_name);
4977+ } else if (*qs == 's') {
4978+ cmp_result = a->st.st_size == b->st.st_size ? 0 :
4979+ a->st.st_size > b->st.st_size ? 1 : -1;
4980+ } else if (*qs == 'd') {
4981+ cmp_result = a->st.st_mtime == b->st.st_mtime ? 0 :
4982+ a->st.st_mtime > b->st.st_mtime ? 1 : -1;
4983+ }
4984+
4985+ return qs[1] == 'd' ? -cmp_result : cmp_result;
4986+}
4987+
4988+static void send_directory_listing(struct connection *conn, const char *dir) {
4989+ struct dir_entry *arr = NULL;
4990+ int i, num_entries, sort_direction = conn->mg_conn.query_string != NULL &&
4991+ conn->mg_conn.query_string[1] == 'd' ? 'a' : 'd';
4992+
4993+ mg_send_header(&conn->mg_conn, "Transfer-Encoding", "chunked");
4994+ mg_send_header(&conn->mg_conn, "Content-Type", "text/html; charset=utf-8");
4995+
4996+ mg_printf_data(&conn->mg_conn,
4997+ "<html><head><title>Index of %s</title>"
4998+ "<style>th {text-align: left;}</style></head>"
4999+ "<body><h1>Index of %s</h1><pre><table cellpadding=\"0\">"
5000+ "<tr><th><a href=\"?n%c\">Name</a></th>"
The diff has been truncated for viewing.

Subscribers

People subscribed via source and target branches

to all changes: