Merge lp:~phablet-team/media-hub/oxide-support into lp:media-hub
- oxide-support
- Merge into trunk
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 | ||||
Related bugs: |
|
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.
Description of the change
PS Jenkins bot (ps-jenkins) wrote : | # |
- 45. By Justin McPherson <justin@phablet-dev>
-
Fix unit test for open_resource_
for_uri( ) headers variant.
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:45
http://
Executed test runs:
SUCCESS: http://
FAILURE: http://
FAILURE: http://
Click here to trigger a rebuild:
http://
- 46. By Justin McPherson <justin@phablet-dev>
-
Use local webserver for headers test.
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:46
http://
Executed test runs:
FAILURE: http://
FAILURE: http://
FAILURE: http://
Click here to trigger a rebuild:
http://
- 47. By Justin McPherson <justin@phablet-dev>
-
Satisy CI.
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:47
http://
Executed test runs:
FAILURE: http://
SUCCESS: http://
deb: http://
FAILURE: http://
Click here to trigger a rebuild:
http://
- 48. By Justin McPherson <justin@phablet-dev>
-
Fix problem with test web serve.
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:48
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
Jim Hodapp (jhodapp) wrote : | # |
Looks good - four inline comments to look at.
Justin McPherson (justinmcp) : | # |
- 49. By Justin McPherson <justin@phablet-dev>
-
Result of review.
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:49
http://
Executed test runs:
FAILURE: http://
FAILURE: http://
FAILURE: http://
Click here to trigger a rebuild:
http://
Jim Hodapp (jhodapp) : | # |
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.
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:51
http://
Executed test runs:
FAILURE: http://
SUCCESS: http://
deb: http://
FAILURE: http://
Click here to trigger a rebuild:
http://
- 52. By Justin McPherson <justin@phablet-dev>
-
Merge from trunk.
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:52
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
deb: http://
FAILURE: http://
Click here to trigger a rebuild:
http://
- 53. By Justin McPherson <justin@phablet-dev>
-
Merge from trunk.
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:53
http://
Executed test runs:
FAILURE: http://
FAILURE: http://
FAILURE: http://
Click here to trigger a rebuild:
http://
- 54. By Jim Hodapp
-
Merged with prerequisite branch to merging into trunk
- 55. By Jim Hodapp
-
Merged with trunk
Unmerged revisions
Preview Diff
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, ¤t_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> %s</td><td> %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>" |
FAILED: Continuous integration, rev:44 jenkins. qa.ubuntu. com/job/ media-hub- ci/135/ jenkins. qa.ubuntu. com/job/ media-hub- utopic- amd64-ci/ 77/console jenkins. qa.ubuntu. com/job/ media-hub- utopic- armhf-ci/ 76/console jenkins. qa.ubuntu. com/job/ media-hub- utopic- i386-ci/ 75/console
http://
Executed test runs:
FAILURE: http://
FAILURE: http://
FAILURE: http://
Click here to trigger a rebuild: s-jenkins. ubuntu- ci:8080/ job/media- hub-ci/ 135/rebuild
http://