reSIProcate
SIP stack, Dialog Usage Manager and related libraries
Loading...
Searching...
No Matches
HttpConnection.cxx
Go to the documentation of this file.
1#include "rutil/ResipAssert.h"
2
3#include "rutil/Data.hxx"
5#include "rutil/Socket.hxx"
8#include "rutil/Logger.hxx"
10#include "rutil/DnsUtil.hxx"
11#include "rutil/ParseBuffer.hxx"
12
14#include "repro/HttpBase.hxx"
16
17
18using namespace resip;
19using namespace repro;
20using namespace std;
21
22#define RESIPROCATE_SUBSYSTEM Subsystem::REPRO
23
24int HttpConnection::nextPageNumber=1;
25
26namespace
27{
28// Canned HTTP status page, styled to match the admin pages (see
29// repro/webadmin/pageOutlinePre.html for the palette these colours come from).
30Data
31statusPage(const char* code, const char* title, const char* detail)
32{
33 Data page;
34 {
35 DataStream s(page);
36 s << "<!DOCTYPE html>"
37 "<html lang=\"en\"><head><meta charset=\"utf-8\" />"
38 "<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />"
39 "<title>" << code << " " << title << "</title><style>"
40 "*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}"
41 "body{background:#0a0c0f;color:#c8d0da;"
42 "font-family:'IBM Plex Mono',ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;"
43 "font-size:13px;line-height:1.55;min-height:100vh;display:flex;align-items:center;"
44 "justify-content:center;padding:24px}"
45 ".card{background:#10141a;border:1px solid #1e2730;border-radius:3px;"
46 "padding:26px 28px;max-width:460px;width:100%}"
47 ".code{font-size:11px;font-weight:600;letter-spacing:.18em;color:#ffab00;"
48 "text-transform:uppercase;margin-bottom:8px}"
49 ".title{font-size:17px;font-weight:600;letter-spacing:.04em}"
50 "p{color:#7a8899;font-size:12px;margin-top:14px}"
51 "a{color:#40c4ff;text-decoration:none}a:hover{text-decoration:underline}"
52 "</style></head><body><div class=\"card\">"
53 "<div class=\"code\">HTTP " << code << "</div>"
54 "<div class=\"title\">" << title << "</div>"
55 "<p>" << detail << "</p>"
56 "<p><a href=\"/index.html\">&larr; Back to Repro</a></p>"
57 "</div></body></html>";
58 s.flush();
59 }
60 return page;
61}
62}
63
64
65
66
68 mHttpBase( base ),
69 mPageNumber(nextPageNumber++),
70 mSock(pSock),
71 mParsedRequest(false)
72{
73 resip_assert( mSock > 0 );
74}
75
76
78{
79 resip_assert( mSock > 0 );
80#ifdef WIN32
81 closesocket(mSock); mSock=0;
82#else
83 close(mSock); mSock=0;
84#endif
85}
86
87
88void
90{
91 if ( !mTxBuffer.empty() )
92 {
93 fdset.setWrite(mSock);
94 }
95 fdset.setRead(mSock);
96}
97
98
99bool
101{
102 if ( fdset.hasException(mSock) )
103 {
104 int errNum = 0;
105 int errNumSize = sizeof(errNum);
106 getsockopt(mSock,SOL_SOCKET,SO_ERROR,(char *)&errNum,(socklen_t *)&errNumSize);
107 InfoLog (<< "Exception reading from socket "
108 << (int)mSock << " code: " << errNum << "; closing connection");
109 return false;
110 }
111
112 if ( fdset.readyToRead( mSock ) )
113 {
114 bool ok = processSomeReads();
115 if ( !ok )
116 {
117 return false;
118 }
119 }
120 if ( (!mTxBuffer.empty()) && fdset.readyToWrite( mSock ) )
121 {
122 bool ok = processSomeWrites();
123 if ( !ok )
124 {
125 return false;
126 }
127 }
128
129 return true;
130}
131
132
133void
134HttpConnection::setPage(const Data& pPage,int response,const Mime& pType)
135{
136 Data page(pPage);
137
138 // For REST responses (application/json), callers supply the body and we
139 // must not substitute a canned HTML body.
140 const bool isJson = (pType.type() == "application" && pType.subType() == "json");
141
142 switch (response)
143 {
144 case 401:
145 {
146 mTxBuffer += "HTTP/1.0 401 Unauthorized"; mTxBuffer += Symbols::CRLF;
147
148 if (!isJson)
149 {
150 page = statusPage("401", "Unauthorized",
151 "Valid administrator credentials are required to view this page.");
152 }
153 }
154 break;
155
156 case 404:
157 {
158 mTxBuffer += "HTTP/1.0 404 Not Found"; mTxBuffer += Symbols::CRLF;
159
160 if (!isJson)
161 {
162 page = statusPage("404", "Not Found",
163 "The requested page does not exist on this server.");
164 }
165 }
166 break;
167
168 case 301:
169 {
170 mTxBuffer += "HTTP/1.0 301 Moved Permanently"; mTxBuffer += Symbols::CRLF;
171 mTxBuffer += "Location: /index.html"; mTxBuffer += Symbols::CRLF;
172
173 page = statusPage("301", "Moved Permanently",
174 "This page has moved. Redirecting to the Repro home page.");
175 }
176 break;
177
178 case 200:
179 {
180 mTxBuffer += "HTTP/1.0 200 OK" ; mTxBuffer += Symbols::CRLF;
181 }
182 break;
183
184 case 400:
185 {
186 mTxBuffer += "HTTP/1.0 400 Bad Request" ; mTxBuffer += Symbols::CRLF;
187 }
188 break;
189
190 case 405:
191 {
192 mTxBuffer += "HTTP/1.0 405 Method Not Allowed" ; mTxBuffer += Symbols::CRLF;
193 }
194 break;
195
196 case 500:
197 {
198 mTxBuffer += "HTTP/1.0 500 Server failure" ; mTxBuffer += Symbols::CRLF;
199 }
200 break;
201
202 case 501:
203 {
204 mTxBuffer += "HTTP/1.0 501 Not Implemented" ; mTxBuffer += Symbols::CRLF;
205 }
206 break;
207
208 case 503:
209 {
210 mTxBuffer += "HTTP/1.0 503 Service Unavailable" ; mTxBuffer += Symbols::CRLF;
211 }
212 break;
213
214 case 504:
215 {
216 mTxBuffer += "HTTP/1.0 504 Gateway Timeout" ; mTxBuffer += Symbols::CRLF;
217 }
218 break;
219
220 default:
221 {
222 resip_assert(0);
223
224 Data resp;
225 {
226 DataStream s(resp);
227 s << response;
228 s.flush();
229 }
230
231 mTxBuffer += "HTTP/1.0 ";
232 mTxBuffer += resp;
233 mTxBuffer += "OK" ; mTxBuffer += Symbols::CRLF;
234 }
235 break;
236 }
237
238 Data len;
239 {
240 DataStream s(len);
241 s << page.size();
242 s.flush();
243 }
244
245 mTxBuffer += "WWW-Authenticate: Basic realm=\"";
246 if ( mHttpBase.mRealm.empty() )
247 {
249 }
250 else
251 {
252 mTxBuffer += mHttpBase.mRealm;
253 }
254 mTxBuffer += "\" ";
255 mTxBuffer += Symbols::CRLF;
256
257 mTxBuffer += "Server: Repro Proxy " ;
258 mTxBuffer += Data(VersionUtils::instance().displayVersion());
259 mTxBuffer += Symbols::CRLF;
260 mTxBuffer += "Mime-version: 1.0 " ; mTxBuffer += Symbols::CRLF;
261 mTxBuffer += "Pragma: no-cache " ; mTxBuffer += Symbols::CRLF;
262 mTxBuffer += "Content-Length: "; mTxBuffer += len; mTxBuffer += Symbols::CRLF;
263
264 mTxBuffer += "Content-Type: " ;
265 mTxBuffer += pType.type() ;
266 mTxBuffer +="/" ;
267 mTxBuffer += pType.subType() ; mTxBuffer += Symbols::CRLF;
268
269 mTxBuffer += Symbols::CRLF;
270
271 mTxBuffer += page;
272}
273
274
275bool
276HttpConnection::processSomeReads()
277{
278 const int bufSize = 8000;
279 char buf[bufSize];
280
281
282#if defined(WIN32)
283 int bytesRead = ::recv(mSock, buf, bufSize, 0);
284#else
285 int bytesRead = ::read(mSock, buf, bufSize);
286#endif
287
288 if (bytesRead == INVALID_SOCKET)
289 {
290 int e = getErrno();
291 switch (e)
292 {
293 case EAGAIN:
294#if EAGAIN != EWOULDBLOCK
295 case EWOULDBLOCK: // Treat EGAIN and EWOULDBLOCK as the same: http://stackoverflow.com/questions/7003234/which-systems-define-eagain-and-ewouldblock-as-different-values
296#endif
297 InfoLog (<< "No data ready to read");
298 return true;
299 case EINTR:
300 InfoLog (<< "The call was interrupted by a signal before any data was read.");
301 break;
302 case EIO:
303 InfoLog (<< "I/O error");
304 break;
305 case EBADF:
306 InfoLog (<< "fd is not a valid file descriptor or is not open for reading.");
307 break;
308 case EINVAL:
309 InfoLog (<< "fd is attached to an object which is unsuitable for reading.");
310 break;
311 case EFAULT:
312 InfoLog (<< "buf is outside your accessible address space.");
313 break;
314 default:
315 InfoLog (<< "Some other error");
316 break;
317 }
318 InfoLog (<< "Failed read on " << (int)mSock << " " << strError(e));
319 return false;
320 }
321 else if (bytesRead == 0)
322 {
323 InfoLog (<< "Connection closed by remote " );
324 return false;
325 }
326
327 //DebugLog (<< "HttpConnection::processSomeReads() "
328 // << " read=" << bytesRead);
329
330 mRxBuffer += Data( buf, bytesRead );
331
332 tryParse();
333
334 return true;
335}
336
337
338void
339HttpConnection::tryParse()
340{
341 //DebugLog (<< "parse " << mRxBuffer );
342
343 ParseBuffer pb(mRxBuffer);
344
345 // See if we have the entire message
346 pb.skipToChars(Symbols::CRLFCRLF);
347 if(pb.eof())
348 {
349 // End of message not found - keep reading
350 return;
351 }
352 pb.reset(pb.start());
353
354 // Capture the HTTP method (first token) so that the subclass can
355 // dispatch based on GET/POST/PUT/DELETE for REST endpoints.
356 const char* methodStart = pb.position();
357 pb.skipToChar(Symbols::SPACE[0]);
358
359 if (pb.eof())
360 {
361 // parse failed - just return
362 return;
363 }
364
365 Data method;
366 pb.data(method, methodStart);
367
368 const char* start = pb.skipWhitespace();
369 pb.skipToChar(Symbols::SPACE[0]);
370
371 if (pb.eof())
372 {
373 // parse failed - just return
374 return;
375 }
376
377 Data uri;
378 pb.data( uri, start );
379
380 DebugLog (<< "parse found method " << method << " URI " << uri );
381 mParsedRequest = true;
382
383
384 Data user;
385 Data password;
386
387 try
388 {
389 pb.skipToChars("Authorization");
390 if (!pb.eof())
391 {
392 if ( pb.eof() ) DebugLog( << "Did not find Authorization header" );
393 pb.skipToChars( "Basic" ); pb.skipN(6);
394 if ( pb.eof() ) DebugLog( << "Did not find Authorization basic " );
395 pb.skipWhitespace();
396 if ( pb.eof() ) DebugLog( << "Something weird in Auhtorization header " );
397 if ( !pb.eof() )
398 {
399 const char* a = pb.position();
400 pb.skipNonWhitespace();
401 Data buf = pb.data(a);
402
403 DebugLog (<< "parse found basic base64 auth data of " << buf );
404 Data auth = buf.base64decode();
405
406 //DebugLog (<< "parse found basic auth data of " << auth );
407
408 ParseBuffer p(auth);
409 const char* a1 = p.position();
410 p.skipToChar(':');
411 user = p.data(a1);
412 const char* a2 = p.skipChar(':');
413 p.skipToEnd();
414 password = p.data(a2);
415
416 //DebugLog (<< "parse found basic auth data with user=" << user
417 // << " password=" << password );
418 }
419 }
420 }
421 catch ( ... )
422 {
423 ErrLog (<< "Some problem finding Authorization header in HTTP request" );
424 }
425
426 mHttpBase.buildPage(method,uri,mPageNumber,user,password);
427}
428
429
430bool
431HttpConnection::processSomeWrites()
432{
433 if ( mTxBuffer.empty() )
434 {
435 return true;
436 }
437
438 //DebugLog (<< "Writing " << mTxBuffer );
439
440#if defined(WIN32)
441 int bytesWritten = ::send( mSock, mTxBuffer.data(), (int)mTxBuffer.size(), 0);
442#else
443 int bytesWritten = ::write(mSock, mTxBuffer.data(), (int)mTxBuffer.size() );
444#endif
445
446 if (bytesWritten == INVALID_SOCKET)
447 {
448 int e = getErrno();
449 InfoLog (<< "HttpConnection failed write on " << mSock << " " << strError(e));
450
451 return false;
452 }
453
454 if (bytesWritten == (int)mTxBuffer.size() )
455 {
456 DebugLog (<< "Wrote it all" );
457 mTxBuffer = Data::Empty;
458
459 return false; // return false causes connection to close and clean up
460 }
461 else
462 {
463 Data rest = mTxBuffer.substr(bytesWritten);
464 mTxBuffer = rest;
465 DebugLog( << "Wrote " << bytesWritten << " bytes - still need to do " << mTxBuffer );
466 }
467
468 return true;
469}
470
471
472/* ====================================================================
473 * The Vovida Software License, Version 1.0
474 *
475 * Copyright (c) 2026 SIP Spectrum, Inc. https://www.sipspectrum.com
476 * Copyright (c) 2000 Vovida Networks, Inc. All rights reserved.
477 *
478 * Redistribution and use in source and binary forms, with or without
479 * modification, are permitted provided that the following conditions
480 * are met:
481 *
482 * 1. Redistributions of source code must retain the above copyright
483 * notice, this list of conditions and the following disclaimer.
484 *
485 * 2. Redistributions in binary form must reproduce the above copyright
486 * notice, this list of conditions and the following disclaimer in
487 * the documentation and/or other materials provided with the
488 * distribution.
489 *
490 * 3. The names "VOCAL", "Vovida Open Communication Application Library",
491 * and "Vovida Open Communication Application Library (VOCAL)" must
492 * not be used to endorse or promote products derived from this
493 * software without prior written permission. For written
494 * permission, please contact vocal@vovida.org.
495 *
496 * 4. Products derived from this software may not be called "VOCAL", nor
497 * may "VOCAL" appear in their name, without prior written
498 * permission of Vovida Networks, Inc.
499 *
500 * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESSED OR IMPLIED
501 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
502 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND
503 * NON-INFRINGEMENT ARE DISCLAIMED. IN NO EVENT SHALL VOVIDA
504 * NETWORKS, INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT DAMAGES
505 * IN EXCESS OF $1,000, NOR FOR ANY INDIRECT, INCIDENTAL, SPECIAL,
506 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
507 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
508 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
509 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
510 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
511 * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
512 * DAMAGE.
513 *
514 * ====================================================================
515 *
516 * This software consists of voluntary contributions made by Vovida
517 * Networks, Inc. and many individuals on behalf of Vovida Networks,
518 * Inc. For more information on Vovida Networks, Inc., please see
519 * <http://www.vovida.org/>.
520 *
521 */
#define DebugLog(args_)
Definition Logger.hxx:128
#define InfoLog(args_)
Definition Logger.hxx:131
#define ErrLog(args_)
Definition Logger.hxx:137
#define resip_assert(x)
This file needs to work for both C and C++ code.
Handles cross-platform sockets compatibility.
#define INVALID_SOCKET
Definition Socket.hxx:95
static int getErrno()
int Socket
const resip::Data mRealm
Definition HttpBase.hxx:38
virtual void buildPage(const resip::Data &method, const resip::Data &uri, int pageNumber, const resip::Data &user, const resip::Data &password)=0
bool process(resip::FdSet &fdset)
void buildFdSet(resip::FdSet &fdset)
HttpConnection(HttpBase &webAdmin, resip::Socket pSock)
void setPage(const resip::Data &page, int response, const resip::Mime &pType)
static const VersionUtils & instance()
An iostream that operates on an existing Data.
An alternative to std::string, encapsulates an arbitrary buffer of bytes.
Definition Data.hxx:90
Data base64decode() const
Performs RFC 3548 Base 64 decoding of the contents of this data.
Definition Data.cxx:2607
static const Data Empty
Constant that represents a zero-length data.
Definition Data.hxx:882
Data substr(size_type first, size_type count=Data::npos) const
Copies a portion of this Data into a new Data.
Definition Data.cxx:1932
bool empty() const noexcept
Checks whether the Data is empty.
Definition Data.hxx:591
size_type size() const noexcept
Returns the number of bytes in this Data.
Definition Data.hxx:599
const char * data() const noexcept
Returns a pointer to the contents of this Data.
Definition Data.hxx:608
static const Data & getLocalHostName()
Definition DnsUtil.cxx:118
Object-oriented wrapper for your platform's file-descriptor set.
Definition Socket.hxx:162
bool readyToWrite(Socket fd)
Definition Socket.hxx:194
bool readyToRead(Socket fd)
Definition Socket.hxx:189
void setRead(Socket fd)
Definition Socket.hxx:204
bool hasException(Socket fd)
Definition Socket.hxx:199
void setWrite(Socket fd)
Definition Socket.hxx:216
Represents the "media-type" element in the RFC 3261 grammar.
Definition Mime.hxx:20
const Data & subType() const
Definition Mime.cxx:100
const Data & type() const
Definition Mime.cxx:94
Provides string-parsing functionality with protection from buffer overflow.
static const char * SPACE
Definition Symbols.hxx:34
static const char * CRLFCRLF
Definition Symbols.hxx:22
static const char * CRLF
Definition Symbols.hxx:21
This Class is used to implement a primitive form of RPC using loose XML formatting.
dcm! – add UnusedChecking(_enum) below;
std::string strError(int err)
Definition compat.hxx:274