| 
									
										
										
										
											2017-08-23 18:47:07 +02:00
										 |  |  | /**
 | 
					
						
							|  |  |  |   @file | 
					
						
							|  |  |  |   @author Stefan Frings | 
					
						
							|  |  |  | */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #include "httpconnectionhandler.h"
 | 
					
						
							|  |  |  | #include "httpresponse.h"
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-11-11 09:32:15 +01:00
										 |  |  | using namespace qtwebapp; | 
					
						
							| 
									
										
										
										
											2017-08-23 18:47:07 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | HttpConnectionHandler::HttpConnectionHandler(QSettings* settings, HttpRequestHandler* requestHandler, QSslConfiguration* sslConfiguration) | 
					
						
							| 
									
										
										
										
											2017-11-13 01:36:00 +01:00
										 |  |  |     : QThread(), useQtSettings(true) | 
					
						
							| 
									
										
										
										
											2017-08-23 18:47:07 +02:00
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2017-11-18 10:15:29 +01:00
										 |  |  |     Q_ASSERT(settings != 0); | 
					
						
							|  |  |  |     Q_ASSERT(requestHandler != 0); | 
					
						
							|  |  |  |     this->settings = settings; | 
					
						
							|  |  |  |     this->listenerSettings = 0; | 
					
						
							|  |  |  |     this->requestHandler = requestHandler; | 
					
						
							|  |  |  |     this->sslConfiguration = sslConfiguration; | 
					
						
							|  |  |  |     currentRequest = 0; | 
					
						
							| 
									
										
										
										
											2017-11-13 01:36:00 +01:00
										 |  |  |     busy=false; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // Create TCP or SSL socket
 | 
					
						
							|  |  |  |     createSocket(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // execute signals in my own thread
 | 
					
						
							|  |  |  |     moveToThread(this); | 
					
						
							|  |  |  |     socket->moveToThread(this); | 
					
						
							|  |  |  |     readTimer.moveToThread(this); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // Connect signals
 | 
					
						
							|  |  |  |     connect(socket, SIGNAL(readyRead()), SLOT(read())); | 
					
						
							|  |  |  |     connect(socket, SIGNAL(disconnected()), SLOT(disconnected())); | 
					
						
							|  |  |  |     connect(&readTimer, SIGNAL(timeout()), SLOT(readTimeout())); | 
					
						
							|  |  |  |     readTimer.setSingleShot(true); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     qDebug("HttpConnectionHandler (%p): constructed", this); | 
					
						
							|  |  |  |     this->start(); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-11-18 10:15:29 +01:00
										 |  |  | HttpConnectionHandler::HttpConnectionHandler(const HttpListenerSettings* settings, HttpRequestHandler* requestHandler, QSslConfiguration* sslConfiguration) | 
					
						
							| 
									
										
										
										
											2017-11-13 01:36:00 +01:00
										 |  |  |     : QThread(), useQtSettings(false) | 
					
						
							|  |  |  | { | 
					
						
							| 
									
										
										
										
											2017-11-18 10:15:29 +01:00
										 |  |  |     Q_ASSERT(settings != 0); | 
					
						
							|  |  |  |     Q_ASSERT(requestHandler != 0); | 
					
						
							|  |  |  |     this->settings = 0; | 
					
						
							|  |  |  |     this->listenerSettings = settings; | 
					
						
							|  |  |  |     this->requestHandler = requestHandler; | 
					
						
							|  |  |  |     this->sslConfiguration = sslConfiguration; | 
					
						
							|  |  |  |     currentRequest = 0; | 
					
						
							|  |  |  |     busy = false; | 
					
						
							| 
									
										
										
										
											2017-08-23 18:47:07 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     // Create TCP or SSL socket
 | 
					
						
							|  |  |  |     createSocket(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // execute signals in my own thread
 | 
					
						
							|  |  |  |     moveToThread(this); | 
					
						
							|  |  |  |     socket->moveToThread(this); | 
					
						
							|  |  |  |     readTimer.moveToThread(this); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // Connect signals
 | 
					
						
							|  |  |  |     connect(socket, SIGNAL(readyRead()), SLOT(read())); | 
					
						
							|  |  |  |     connect(socket, SIGNAL(disconnected()), SLOT(disconnected())); | 
					
						
							|  |  |  |     connect(&readTimer, SIGNAL(timeout()), SLOT(readTimeout())); | 
					
						
							|  |  |  |     readTimer.setSingleShot(true); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     qDebug("HttpConnectionHandler (%p): constructed", this); | 
					
						
							|  |  |  |     this->start(); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | HttpConnectionHandler::~HttpConnectionHandler() | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     quit(); | 
					
						
							|  |  |  |     wait(); | 
					
						
							|  |  |  |     qDebug("HttpConnectionHandler (%p): destroyed", this); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void HttpConnectionHandler::createSocket() | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     // If SSL is supported and configured, then create an instance of QSslSocket
 | 
					
						
							|  |  |  |     #ifndef QT_NO_OPENSSL
 | 
					
						
							|  |  |  |         if (sslConfiguration) | 
					
						
							|  |  |  |         { | 
					
						
							|  |  |  |             QSslSocket* sslSocket=new QSslSocket(); | 
					
						
							|  |  |  |             sslSocket->setSslConfiguration(*sslConfiguration); | 
					
						
							|  |  |  |             socket=sslSocket; | 
					
						
							|  |  |  |             qDebug("HttpConnectionHandler (%p): SSL is enabled", this); | 
					
						
							|  |  |  |             return; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     #endif
 | 
					
						
							|  |  |  |     // else create an instance of QTcpSocket
 | 
					
						
							|  |  |  |     socket=new QTcpSocket(); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void HttpConnectionHandler::run() | 
					
						
							|  |  |  | { | 
					
						
							| 
									
										
										
										
											2018-08-11 23:29:38 +02:00
										 |  |  | #ifdef SUPERVERBOSE
 | 
					
						
							| 
									
										
										
										
											2017-08-23 18:47:07 +02:00
										 |  |  |     qDebug("HttpConnectionHandler (%p): thread started", this); | 
					
						
							| 
									
										
										
										
											2018-08-11 23:29:38 +02:00
										 |  |  | #endif
 | 
					
						
							| 
									
										
										
										
											2017-08-23 18:47:07 +02:00
										 |  |  |     try | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         exec(); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     catch (...) | 
					
						
							|  |  |  |     { | 
					
						
							| 
									
										
										
										
											2019-07-22 23:14:23 +02:00
										 |  |  |         qCritical("HttpConnectionHandler (%p): an uncatched exception occurred in the thread",this); | 
					
						
							| 
									
										
										
										
											2017-08-23 18:47:07 +02:00
										 |  |  |     } | 
					
						
							|  |  |  |     socket->close(); | 
					
						
							|  |  |  |     delete socket; | 
					
						
							|  |  |  |     readTimer.stop(); | 
					
						
							| 
									
										
										
										
											2018-08-11 23:29:38 +02:00
										 |  |  | #ifdef SUPERVERBOSE
 | 
					
						
							| 
									
										
										
										
											2017-08-23 18:47:07 +02:00
										 |  |  |     qDebug("HttpConnectionHandler (%p): thread stopped", this); | 
					
						
							| 
									
										
										
										
											2018-08-11 23:29:38 +02:00
										 |  |  | #endif
 | 
					
						
							| 
									
										
										
										
											2017-08-23 18:47:07 +02:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void HttpConnectionHandler::handleConnection(tSocketDescriptor socketDescriptor) | 
					
						
							|  |  |  | { | 
					
						
							| 
									
										
										
										
											2018-08-11 23:29:38 +02:00
										 |  |  | #ifdef SUPERVERBOSE
 | 
					
						
							| 
									
										
										
										
											2017-08-23 18:47:07 +02:00
										 |  |  |     qDebug("HttpConnectionHandler (%p): handle new connection", this); | 
					
						
							| 
									
										
										
										
											2018-08-11 23:29:38 +02:00
										 |  |  | #endif
 | 
					
						
							| 
									
										
										
										
											2017-08-23 18:47:07 +02:00
										 |  |  |     busy = true; | 
					
						
							|  |  |  |     Q_ASSERT(socket->isOpen()==false); // if not, then the handler is already busy
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     //UGLY workaround - we need to clear writebuffer before reusing this socket
 | 
					
						
							|  |  |  |     //https://bugreports.qt-project.org/browse/QTBUG-28914
 | 
					
						
							|  |  |  |     socket->connectToHost("",0); | 
					
						
							|  |  |  |     socket->abort(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (!socket->setSocketDescriptor(socketDescriptor)) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         qCritical("HttpConnectionHandler (%p): cannot initialize socket: %s", this,qPrintable(socket->errorString())); | 
					
						
							|  |  |  |         return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     #ifndef QT_NO_OPENSSL
 | 
					
						
							|  |  |  |         // Switch on encryption, if SSL is configured
 | 
					
						
							|  |  |  |         if (sslConfiguration) | 
					
						
							|  |  |  |         { | 
					
						
							|  |  |  |             qDebug("HttpConnectionHandler (%p): Starting encryption", this); | 
					
						
							|  |  |  |             ((QSslSocket*)socket)->startServerEncryption(); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     #endif
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // Start timer for read timeout
 | 
					
						
							| 
									
										
										
										
											2017-11-18 10:15:29 +01:00
										 |  |  |     int readTimeout = useQtSettings ? settings->value("readTimeout",10000).toInt() : listenerSettings->readTimeout; | 
					
						
							| 
									
										
										
										
											2017-08-23 18:47:07 +02:00
										 |  |  |     readTimer.start(readTimeout); | 
					
						
							|  |  |  |     // delete previous request
 | 
					
						
							|  |  |  |     delete currentRequest; | 
					
						
							|  |  |  |     currentRequest=0; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | bool HttpConnectionHandler::isBusy() | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     return busy; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void HttpConnectionHandler::setBusy() | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     this->busy = true; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void HttpConnectionHandler::readTimeout() | 
					
						
							|  |  |  | { | 
					
						
							| 
									
										
										
										
											2019-07-22 23:14:23 +02:00
										 |  |  |     qDebug("HttpConnectionHandler (%p): read timeout occurred",this); | 
					
						
							| 
									
										
										
										
											2017-08-23 18:47:07 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     //Commented out because QWebView cannot handle this.
 | 
					
						
							|  |  |  |     //socket->write("HTTP/1.1 408 request timeout\r\nConnection: close\r\n\r\n408 request timeout\r\n");
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     while(socket->bytesToWrite()) socket->waitForBytesWritten(); | 
					
						
							|  |  |  |     socket->disconnectFromHost(); | 
					
						
							|  |  |  |     delete currentRequest; | 
					
						
							|  |  |  |     currentRequest=0; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void HttpConnectionHandler::disconnected() | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     qDebug("HttpConnectionHandler (%p): disconnected", this); | 
					
						
							|  |  |  |     socket->close(); | 
					
						
							|  |  |  |     readTimer.stop(); | 
					
						
							|  |  |  |     busy = false; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void HttpConnectionHandler::read() | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     // The loop adds support for HTTP pipelinig
 | 
					
						
							|  |  |  |     while (socket->bytesAvailable()) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         #ifdef SUPERVERBOSE
 | 
					
						
							|  |  |  |             qDebug("HttpConnectionHandler (%p): read input",this); | 
					
						
							|  |  |  |         #endif
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         // Create new HttpRequest object if necessary
 | 
					
						
							|  |  |  |         if (!currentRequest) | 
					
						
							|  |  |  |         { | 
					
						
							| 
									
										
										
										
											2017-11-18 10:15:29 +01:00
										 |  |  |             if (useQtSettings) { | 
					
						
							|  |  |  |                 currentRequest = new HttpRequest(settings); | 
					
						
							|  |  |  |             } else { | 
					
						
							|  |  |  |                 currentRequest = new HttpRequest(listenerSettings); | 
					
						
							|  |  |  |             } | 
					
						
							| 
									
										
										
										
											2017-08-23 18:47:07 +02:00
										 |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         // Collect data for the request object
 | 
					
						
							|  |  |  |         while (socket->bytesAvailable() && currentRequest->getStatus()!=HttpRequest::complete && currentRequest->getStatus()!=HttpRequest::abort) | 
					
						
							|  |  |  |         { | 
					
						
							|  |  |  |             currentRequest->readFromSocket(socket); | 
					
						
							|  |  |  |             if (currentRequest->getStatus()==HttpRequest::waitForBody) | 
					
						
							|  |  |  |             { | 
					
						
							|  |  |  |                 // Restart timer for read timeout, otherwise it would
 | 
					
						
							|  |  |  |                 // expire during large file uploads.
 | 
					
						
							| 
									
										
										
										
											2017-11-18 10:15:29 +01:00
										 |  |  |                 int readTimeout = useQtSettings ? settings->value("readTimeout",10000).toInt() : listenerSettings->readTimeout; | 
					
						
							| 
									
										
										
										
											2017-08-23 18:47:07 +02:00
										 |  |  |                 readTimer.start(readTimeout); | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         // If the request is aborted, return error message and close the connection
 | 
					
						
							|  |  |  |         if (currentRequest->getStatus()==HttpRequest::abort) | 
					
						
							|  |  |  |         { | 
					
						
							|  |  |  |             socket->write("HTTP/1.1 413 entity too large\r\nConnection: close\r\n\r\n413 Entity too large\r\n"); | 
					
						
							|  |  |  |             while(socket->bytesToWrite()) socket->waitForBytesWritten(); | 
					
						
							|  |  |  |             socket->disconnectFromHost(); | 
					
						
							|  |  |  |             delete currentRequest; | 
					
						
							|  |  |  |             currentRequest=0; | 
					
						
							|  |  |  |             return; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         // If the request is complete, let the request mapper dispatch it
 | 
					
						
							|  |  |  |         if (currentRequest->getStatus()==HttpRequest::complete) | 
					
						
							|  |  |  |         { | 
					
						
							|  |  |  |             readTimer.stop(); | 
					
						
							| 
									
										
										
										
											2018-08-12 15:49:14 +02:00
										 |  |  |             qDebug("HttpConnectionHandler (%p): received request from %s (%s) %s", | 
					
						
							| 
									
										
										
										
											2018-08-11 23:29:38 +02:00
										 |  |  |                     this, | 
					
						
							|  |  |  |                     qPrintable(currentRequest->getPeerAddress().toString()), | 
					
						
							|  |  |  |                     currentRequest->getMethod().toStdString().c_str(), | 
					
						
							|  |  |  |                     currentRequest->getPath().toStdString().c_str()); | 
					
						
							| 
									
										
										
										
											2017-08-23 18:47:07 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |             // Copy the Connection:close header to the response
 | 
					
						
							|  |  |  |             HttpResponse response(socket); | 
					
						
							|  |  |  |             bool closeConnection=QString::compare(currentRequest->getHeader("Connection"),"close",Qt::CaseInsensitive)==0; | 
					
						
							|  |  |  |             if (closeConnection) | 
					
						
							|  |  |  |             { | 
					
						
							|  |  |  |                 response.setHeader("Connection","close"); | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             // In case of HTTP 1.0 protocol add the Connection:close header.
 | 
					
						
							| 
									
										
										
										
											2024-07-10 22:59:13 +02:00
										 |  |  |             // This ensures that the HttpResponse does not activate chunked mode, which is not supported by HTTP 1.0.
 | 
					
						
							| 
									
										
										
										
											2017-08-23 18:47:07 +02:00
										 |  |  |             else | 
					
						
							|  |  |  |             { | 
					
						
							|  |  |  |                 bool http1_0=QString::compare(currentRequest->getVersion(),"HTTP/1.0",Qt::CaseInsensitive)==0; | 
					
						
							|  |  |  |                 if (http1_0) | 
					
						
							|  |  |  |                 { | 
					
						
							|  |  |  |                     closeConnection=true; | 
					
						
							|  |  |  |                     response.setHeader("Connection","close"); | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             // Call the request mapper
 | 
					
						
							|  |  |  |             try | 
					
						
							|  |  |  |             { | 
					
						
							|  |  |  |                 requestHandler->service(*currentRequest, response); | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             catch (...) | 
					
						
							|  |  |  |             { | 
					
						
							| 
									
										
										
										
											2019-07-22 23:14:23 +02:00
										 |  |  |                 qCritical("HttpConnectionHandler (%p): An uncatched exception occurred in the request handler",this); | 
					
						
							| 
									
										
										
										
											2017-08-23 18:47:07 +02:00
										 |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             // Finalize sending the response if not already done
 | 
					
						
							|  |  |  |             if (!response.hasSentLastPart()) | 
					
						
							|  |  |  |             { | 
					
						
							|  |  |  |                 response.write(QByteArray(),true); | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-08-11 23:29:38 +02:00
										 |  |  | #ifdef SUPERVERBOSE
 | 
					
						
							| 
									
										
										
										
											2017-08-23 18:47:07 +02:00
										 |  |  |             qDebug("HttpConnectionHandler (%p): finished request",this); | 
					
						
							| 
									
										
										
										
											2018-08-11 23:29:38 +02:00
										 |  |  | #endif
 | 
					
						
							| 
									
										
										
										
											2017-08-23 18:47:07 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |             // Find out whether the connection must be closed
 | 
					
						
							|  |  |  |             if (!closeConnection) | 
					
						
							|  |  |  |             { | 
					
						
							|  |  |  |                 // Maybe the request handler or mapper added a Connection:close header in the meantime
 | 
					
						
							|  |  |  |                 bool closeResponse=QString::compare(response.getHeaders().value("Connection"),"close",Qt::CaseInsensitive)==0; | 
					
						
							|  |  |  |                 if (closeResponse==true) | 
					
						
							|  |  |  |                 { | 
					
						
							|  |  |  |                     closeConnection=true; | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |                 else | 
					
						
							|  |  |  |                 { | 
					
						
							|  |  |  |                     // If we have no Content-Length header and did not use chunked mode, then we have to close the
 | 
					
						
							|  |  |  |                     // connection to tell the HTTP client that the end of the response has been reached.
 | 
					
						
							|  |  |  |                     bool hasContentLength=response.getHeaders().contains("Content-Length"); | 
					
						
							|  |  |  |                     if (!hasContentLength) | 
					
						
							|  |  |  |                     { | 
					
						
							|  |  |  |                         bool hasChunkedMode=QString::compare(response.getHeaders().value("Transfer-Encoding"),"chunked",Qt::CaseInsensitive)==0; | 
					
						
							|  |  |  |                         if (!hasChunkedMode) | 
					
						
							|  |  |  |                         { | 
					
						
							|  |  |  |                             closeConnection=true; | 
					
						
							|  |  |  |                         } | 
					
						
							|  |  |  |                     } | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             // Close the connection or prepare for the next request on the same connection.
 | 
					
						
							|  |  |  |             if (closeConnection) | 
					
						
							|  |  |  |             { | 
					
						
							|  |  |  |                 while(socket->bytesToWrite()) socket->waitForBytesWritten(); | 
					
						
							|  |  |  |                 socket->disconnectFromHost(); | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             else | 
					
						
							|  |  |  |             { | 
					
						
							|  |  |  |                 // Start timer for next request
 | 
					
						
							| 
									
										
										
										
											2017-11-18 10:15:29 +01:00
										 |  |  |                 int readTimeout = useQtSettings ? settings->value("readTimeout",10000).toInt() : listenerSettings->readTimeout; | 
					
						
							| 
									
										
										
										
											2017-08-23 18:47:07 +02:00
										 |  |  |                 readTimer.start(readTimeout); | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             delete currentRequest; | 
					
						
							|  |  |  |             currentRequest=0; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | } |