1 /*
2 * Copyright 2008-2018 Max Kellermann <max.kellermann@gmail.com>
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 *
8 * - Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 *
11 * - Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the
14 * distribution.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
19 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
20 * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
21 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
23 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
25 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
27 * OF THE POSSIBILITY OF SUCH DAMAGE.
28 */
29
30 #include "config.h"
31 #include "Request.hxx"
32 #include "Global.hxx"
33 #include "Handler.hxx"
34 #include "event/Call.hxx"
35 #include "util/RuntimeError.hxx"
36 #include "util/StringStrip.hxx"
37 #include "util/StringView.hxx"
38 #include "util/CharUtil.hxx"
39 #include "Version.h"
40
41 #include <curl/curl.h>
42
43 #include <algorithm>
44 #include <cassert>
45
46 #include <string.h>
47
CurlRequest(CurlGlobal & _global,CurlResponseHandler & _handler)48 CurlRequest::CurlRequest(CurlGlobal &_global,
49 CurlResponseHandler &_handler)
50 :global(_global), handler(_handler)
51 {
52 error_buffer[0] = 0;
53
54 easy.SetPrivate((void *)this);
55 easy.SetUserAgent("Music Player Daemon " VERSION);
56 easy.SetHeaderFunction(_HeaderFunction, this);
57 easy.SetWriteFunction(WriteFunction, this);
58 #if !defined(ANDROID) && !defined(_WIN32)
59 easy.SetOption(CURLOPT_NETRC, 1L);
60 #endif
61 easy.SetErrorBuffer(error_buffer);
62 easy.SetNoProgress();
63 easy.SetNoSignal();
64 easy.SetConnectTimeout(10);
65 easy.SetOption(CURLOPT_HTTPAUTH, (long) CURLAUTH_ANY);
66 }
67
~CurlRequest()68 CurlRequest::~CurlRequest() noexcept
69 {
70 FreeEasy();
71 }
72
73 void
Start()74 CurlRequest::Start()
75 {
76 assert(!registered);
77
78 global.Add(*this);
79 registered = true;
80 }
81
82 void
StartIndirect()83 CurlRequest::StartIndirect()
84 {
85 BlockingCall(global.GetEventLoop(), [this](){
86 Start();
87 });
88 }
89
90 void
Stop()91 CurlRequest::Stop() noexcept
92 {
93 if (!registered)
94 return;
95
96 global.Remove(*this);
97 registered = false;
98 }
99
100 void
StopIndirect()101 CurlRequest::StopIndirect()
102 {
103 BlockingCall(global.GetEventLoop(), [this](){
104 Stop();
105 });
106 }
107
108 void
FreeEasy()109 CurlRequest::FreeEasy() noexcept
110 {
111 if (!easy)
112 return;
113
114 Stop();
115 easy = nullptr;
116 }
117
118 void
Resume()119 CurlRequest::Resume() noexcept
120 {
121 assert(registered);
122
123 easy.Unpause();
124
125 global.InvalidateSockets();
126 }
127
128 void
FinishHeaders()129 CurlRequest::FinishHeaders()
130 {
131 if (state != State::HEADERS)
132 return;
133
134 state = State::BODY;
135
136 long status = 0;
137 easy.GetInfo(CURLINFO_RESPONSE_CODE, &status);
138
139 handler.OnHeaders(status, std::move(headers));
140 }
141
142 void
FinishBody()143 CurlRequest::FinishBody()
144 {
145 FinishHeaders();
146
147 if (state != State::BODY)
148 return;
149
150 state = State::CLOSED;
151 handler.OnEnd();
152 }
153
154 void
Done(CURLcode result)155 CurlRequest::Done(CURLcode result) noexcept
156 {
157 Stop();
158
159 try {
160 if (result != CURLE_OK) {
161 StripRight(error_buffer);
162 const char *msg = error_buffer;
163 if (*msg == 0)
164 msg = curl_easy_strerror(result);
165 throw FormatRuntimeError("CURL failed: %s", msg);
166 }
167
168 FinishBody();
169 } catch (...) {
170 state = State::CLOSED;
171 handler.OnError(std::current_exception());
172 }
173 }
174
175 [[gnu::pure]]
176 static bool
IsResponseBoundaryHeader(StringView s)177 IsResponseBoundaryHeader(StringView s) noexcept
178 {
179 return s.size > 5 && (s.StartsWith("HTTP/") ||
180 /* the proprietary "ICY 200 OK" is
181 emitted by Shoutcast */
182 s.StartsWith("ICY 2"));
183 }
184
185 inline void
HeaderFunction(StringView s)186 CurlRequest::HeaderFunction(StringView s) noexcept
187 {
188 if (state > State::HEADERS)
189 return;
190
191 if (IsResponseBoundaryHeader(s)) {
192 /* this is the boundary to a new response, for example
193 after a redirect */
194 headers.clear();
195 return;
196 }
197
198 const char *header = s.data;
199 const char *end = StripRight(header, header + s.size);
200
201 const char *value = s.Find(':');
202 if (value == nullptr)
203 return;
204
205 std::string name(header, value);
206 std::transform(name.begin(), name.end(), name.begin(),
207 static_cast<char(*)(char)>(ToLowerASCII));
208
209 /* skip the colon */
210
211 ++value;
212
213 /* strip the value */
214
215 value = StripLeft(value, end);
216 end = StripRight(value, end);
217
218 headers.emplace(std::move(name), std::string(value, end));
219 }
220
221 size_t
_HeaderFunction(char * ptr,size_t size,size_t nmemb,void * stream)222 CurlRequest::_HeaderFunction(char *ptr, size_t size, size_t nmemb,
223 void *stream) noexcept
224 {
225 CurlRequest &c = *(CurlRequest *)stream;
226
227 size *= nmemb;
228
229 c.HeaderFunction({ptr, size});
230 return size;
231 }
232
233 inline size_t
DataReceived(const void * ptr,size_t received_size)234 CurlRequest::DataReceived(const void *ptr, size_t received_size) noexcept
235 {
236 assert(received_size > 0);
237
238 try {
239 FinishHeaders();
240 handler.OnData({ptr, received_size});
241 return received_size;
242 } catch (CurlResponseHandler::Pause) {
243 return CURL_WRITEFUNC_PAUSE;
244 }
245
246 }
247
248 size_t
WriteFunction(char * ptr,size_t size,size_t nmemb,void * stream)249 CurlRequest::WriteFunction(char *ptr, size_t size, size_t nmemb,
250 void *stream) noexcept
251 {
252 CurlRequest &c = *(CurlRequest *)stream;
253
254 size *= nmemb;
255 if (size == 0)
256 return 0;
257
258 return c.DataReceived(ptr, size);
259 }
260