qint64 THttpSocket::writeRawData(const char *data, qint64 size) { qint64 total = 0; if (Q_UNLIKELY(!data || size == 0)) { return total; } for (;;) { if (QTcpSocket::bytesToWrite() > SEND_BUF_SIZE * 3 / 4) { if (Q_UNLIKELY(!waitForBytesWritten())) { tWarn("socket error: waitForBytesWritten function [%s]", qPrintable(errorString())); break; } } qint64 written = QTcpSocket::write(data + total, qMin(size - total, WRITE_LENGTH)); if (Q_UNLIKELY(written <= 0)) { tWarn("socket write error: total:%d (%d)", (int)total, (int)written); return -1; } total += written; if (total >= size) { break; } } idleElapsed = std::time(nullptr); return total; }
/*! Sets the validation rule for the key \a key and sets the error message of it to \a errorMessage. */ void TFormValidator::setRule(const QString &key, Tf::ValidationRule rule, double val, const QString &errorMessage) { // arg check switch ((int)rule) { case Tf::Required: case Tf::MaxLength: case Tf::MinLength: case Tf::IntMax: case Tf::IntMin: case Tf::EmailAddress: case Tf::Url: case Tf::Date: case Tf::Time: case Tf::DateTime: tWarn("Validation: Bad rule spedified [key:%s rule:%d]. Use another setRule method.", qPrintable(key), rule); return; case Tf::Pattern: tWarn("Validation: Bad rule spedified [key:%s rule:%d]. Use setPatternRule method.", qPrintable(key), rule); return; } removeRule(key, rule); rules.prepend(RuleEntry(key, (int)rule, val, (errorMessage.isEmpty() ? Tf::app()->validationErrorMessage(rule) : errorMessage))); }
/*! Inserts new record into the database, based on the current properties of the object. */ bool TSqlObject::create() { // Sets the values of 'created_at', 'updated_at' or 'modified_at' properties for (int i = metaObject()->propertyOffset(); i < metaObject()->propertyCount(); ++i) { const char *propName = metaObject()->property(i).name(); QByteArray prop = QByteArray(propName).toLower(); if (prop == CreatedAt || prop == UpdatedAt || prop == ModifiedAt) { setProperty(propName, QDateTime::currentDateTime()); } else if (prop == LockRevision) { // Sets the default value of 'revision' property setProperty(propName, 1); // 1 : default value } else { // do nothing } } syncToSqlRecord(); QString autoValName; QSqlRecord record = *this; if (autoValueIndex() >= 0) { autoValName = field(autoValueIndex()).name(); record.remove(autoValueIndex()); // not insert the value of auto-value field } QSqlDatabase &database = Tf::currentSqlDatabase(databaseId()); QString ins = database.driver()->sqlStatement(QSqlDriver::InsertStatement, tableName(), record, false); if (Q_UNLIKELY(ins.isEmpty())) { sqlError = QSqlError(QLatin1String("No fields to insert"), QString(), QSqlError::StatementError); tWarn("SQL statement error, no fields to insert"); return false; } TSqlQuery query(database); bool ret = query.exec(ins); sqlError = query.lastError(); if (Q_LIKELY(ret)) { // Gets the last inserted value of auto-value field if (autoValueIndex() >= 0) { QVariant lastid = query.lastInsertId(); if (!lastid.isValid() && database.driverName().toUpper() == QLatin1String("QPSQL")) { // For PostgreSQL without OIDS ret = query.exec("SELECT LASTVAL()"); sqlError = query.lastError(); if (Q_LIKELY(ret)) { lastid = query.getNextValue(); } } if (lastid.isValid()) { QObject::setProperty(autoValName.toLatin1().constData(), lastid); QSqlRecord::setValue(autoValueIndex(), lastid); } } } return ret; }
bool TSqlObject::create() { // Sets the default value of 'revision' property int index = metaObject()->indexOfProperty(REVISION_PROPERTY_NAME); if (index >= 0) { setProperty(REVISION_PROPERTY_NAME, 1); // 1 : default value } // Sets the values of 'created_at' and 'updated_at' properties for (int i = metaObject()->propertyOffset(); i < metaObject()->propertyCount(); ++i) { const char *propName = metaObject()->property(i).name(); if (QLatin1String("created_at") == propName || QLatin1String("updated_at") == propName) { setProperty(propName, QDateTime::currentDateTime()); } } syncToSqlRecord(); QString ins = TActionContext::currentDatabase().driver()->sqlStatement(QSqlDriver::InsertStatement, tableName(), *static_cast<QSqlRecord *>(this), false); if (ins.isEmpty()) { sqlError = QSqlError(QLatin1String("No fields to insert"), QString(), QSqlError::StatementError); tWarn("SQL statement error, no fields to insert"); return false; } tSystemDebug("SQL statement: %s", qPrintable(ins)); QSqlQuery query(TActionContext::currentDatabase()); bool res = query.exec(ins); sqlError = query.lastError(); if (!res) { tSystemError("SQL insert error: %s", qPrintable(sqlError.text())); } return res; }
bool TSqlObject::remove() { syncToSqlRecord(); QString del = TActionContext::currentDatabase().driver()->sqlStatement(QSqlDriver::DeleteStatement, tableName(), *static_cast<QSqlRecord *>(this), false); if (del.isEmpty()) { sqlError = QSqlError(QLatin1String("Unable to delete row"), QString(), QSqlError::StatementError); return false; } del.append(" WHERE "); int revIndex = metaObject()->indexOfProperty(REVISION_PROPERTY_NAME); if (revIndex >= 0) { bool ok; int revsion = property(REVISION_PROPERTY_NAME).toInt(&ok); if (!ok || revsion <= 0) { sqlError = QSqlError(QLatin1String("Unable to convert the 'revision' property to an int"), QString(), QSqlError::UnknownError); tError("Unable to convert the 'revsion' property to an int, %s", qPrintable(objectName())); return false; } del.append(TSqlQuery::escapeIdentifier(REVISION_PROPERTY_NAME)); del.append("=").append(TSqlQuery::formatValue(revsion)); del.append(" AND "); } const char *pkName = metaObject()->property(metaObject()->propertyOffset() + primaryKeyIndex()).name(); if (primaryKeyIndex() < 0 || !pkName) { QString msg = QString("Not found the primary key for table ") + tableName(); sqlError = QSqlError(msg, QString(), QSqlError::StatementError); tError("%s", qPrintable(msg)); return false; } del.append(TSqlQuery::escapeIdentifier(pkName)); del.append("=").append(TSqlQuery::formatValue(property(pkName))); tSystemDebug("SQL statement: %s", qPrintable(del)); QSqlQuery query(TActionContext::currentDatabase()); bool res = query.exec(del); sqlError = query.lastError(); if (!res) { tSystemError("SQL delete error: %s", qPrintable(sqlError.text())); return false; } // Optimistic lock check if (query.numRowsAffected() != 1) { if (revIndex >= 0) { QString msg = QString("Row was updated or deleted from table ") + tableName() + QLatin1String(" by another transaction"); sqlError = QSqlError(msg, QString(), QSqlError::UnknownError); throw SqlException(msg, __FILE__, __LINE__); } tWarn("Row was deleted by another transaction, %s", qPrintable(tableName())); } clear(); return true; }
/*! \~english Sends the file \a filePath as HTTP response. \~japanese HTTPレスポンスとして、ファイル \a filePath の内容を送信する */ bool TActionController::sendFile(const QString &filePath, const QByteArray &contentType, const QString &name, bool autoRemove) { if (rendered) { tWarn("Has rendered already: %s", qPrintable(className() + '#' + activeAction())); return false; } rendered = true; setStatusCode(200); if (!name.isEmpty()) { QByteArray filename; filename += "attachment; filename=\""; filename += name.toUtf8(); filename += '"'; response.header().setRawHeader("Content-Disposition", filename); } response.setBodyFile(filePath); response.header().setContentType(contentType); if (autoRemove) setAutoRemove(filePath); return true; }
qint64 THttpSocket::writeRawData(const char *data, qint64 size) { qint64 total = 0; for (;;) { qint64 written = QTcpSocket::write(data + total, qMin(size - total, WRITE_LENGTH)); if (written <= 0) { tWarn("socket write error: total:%d (%d)", (int)total, (int)written); return -1; } total += written; if (total >= size) break; if (!waitForBytesWritten()) { tWarn("socket error: waitForBytesWritten function [%s]", qPrintable(errorString())); break; } } return total; }
static TKvsDriver *createDriver(const QString &driverName) { TKvsDriver *ret = 0; if (driverName == QLatin1String("MONGODB")) { ret = new TMongoDriver(); } if (!ret) { tWarn("TKvsDatabase: %s driver not loaded", qPrintable(driverName)); } return ret; }
/*! Returns the module name for multi-processing that is set by the setting \a MultiProcessingModule in the application.ini. */ TWebApplication::MultiProcessingModule TWebApplication::multiProcessingModule() const { if (mpm == Invalid) { QString str = Tf::appSettings()->value(Tf::MultiProcessingModule).toString().toLower(); if (str == "thread") { mpm = Thread; } else if (str == "hybrid") { #ifdef Q_OS_LINUX mpm = Hybrid; #else tSystemWarn("Unsupported MPM: hybrid (Linux only)"); tWarn("Unsupported MPM: hybrid (Linux only)"); mpm = Thread; #endif } else { tSystemWarn("Unsupported MPM: %s", qPrintable(str)); tWarn("Unsupported MPM: %s", qPrintable(str)); mpm = Thread; } } return mpm; }
/*! Synchronizes the properties to the internal record data. This function is for internal use only. */ void TSqlObject::syncToSqlRecord() { QSqlRecord::operator=(Tf::currentSqlDatabase(databaseId()).record(tableName())); const QMetaObject *metaObj = metaObject(); for (int i = metaObj->propertyOffset(); i < metaObj->propertyCount(); ++i) { const char *propName = metaObj->property(i).name(); int idx = indexOf(propName); if (idx >= 0) { QSqlRecord::setValue(idx, QObject::property(propName)); } else { tWarn("invalid name: %s", propName); } } }
/*! \~english Renders the template of the action \a action with the layout \a layout. \~japanese レイアウト \a layout を適用し、アクション \a action のテンプレートを描画する */ bool TActionController::render(const QString &action, const QString &layout) { T_TRACEFUNC(""); if (rendered) { tWarn("Has rendered already: %s", qPrintable(className() + '#' + activeAction())); return false; } rendered = true; // Creates view-object and displays it TDispatcher<TActionView> viewDispatcher(viewClassName(action)); setLayout(layout); response.setBody(renderView(viewDispatcher.object())); return !response.isBodyNull(); }
void TScheduler::run() { _rollback = false; TDatabaseContext::setCurrentDatabaseContext(this); try { // Executes the job job(); if (_rollback) { TDatabaseContext::rollbackTransactions(); } else { TDatabaseContext::commitTransactions(); } } catch (ClientErrorException &e) { tWarn("Caught ClientErrorException: status code:%d", e.statusCode()); tSystemWarn("Caught ClientErrorException: status code:%d", e.statusCode()); } catch (SqlException &e) { tError("Caught SqlException: %s [%s:%d]", qPrintable(e.message()), qPrintable(e.fileName()), e.lineNumber()); tSystemError("Caught SqlException: %s [%s:%d]", qPrintable(e.message()), qPrintable(e.fileName()), e.lineNumber()); } catch (KvsException &e) { tError("Caught KvsException: %s [%s:%d]", qPrintable(e.message()), qPrintable(e.fileName()), e.lineNumber()); tSystemError("Caught KvsException: %s [%s:%d]", qPrintable(e.message()), qPrintable(e.fileName()), e.lineNumber()); } catch (SecurityException &e) { tError("Caught SecurityException: %s [%s:%d]", qPrintable(e.message()), qPrintable(e.fileName()), e.lineNumber()); tSystemError("Caught SecurityException: %s [%s:%d]", qPrintable(e.message()), qPrintable(e.fileName()), e.lineNumber()); } catch (RuntimeException &e) { tError("Caught RuntimeException: %s [%s:%d]", qPrintable(e.message()), qPrintable(e.fileName()), e.lineNumber()); tSystemError("Caught RuntimeException: %s [%s:%d]", qPrintable(e.message()), qPrintable(e.fileName()), e.lineNumber()); } catch (StandardException &e) { tError("Caught StandardException: %s [%s:%d]", qPrintable(e.message()), qPrintable(e.fileName()), e.lineNumber()); tSystemError("Caught StandardException: %s [%s:%d]", qPrintable(e.message()), qPrintable(e.fileName()), e.lineNumber()); } catch (std::exception &e) { tError("Caught Exception: %s", e.what()); tSystemError("Caught Exception: %s", e.what()); } TDatabaseContext::release(); TDatabaseContext::setCurrentDatabaseContext(nullptr); if (_autoDelete && !_timer->isActive()) { connect(this, &TScheduler::finished, this, &QObject::deleteLater); } }
/*! \~english Renders the text \a text with the layout \a layout. \~japanese レイアウト \a layout を適用し、テキストを描画する */ bool TActionController::renderText(const QString &text, bool layoutEnable, const QString &layout) { T_TRACEFUNC(""); if (rendered) { tWarn("Has rendered already: %s", qPrintable(className() + '#' + activeAction())); return false; } rendered = true; // Creates TTextView object and displays it setLayout(layout); setLayoutEnabled(layoutEnable); TTextView *view = new TTextView(text); response.setBody(renderView(view)); delete view; return (!response.isBodyNull()); }
/*! Returns true if the user \a user is allowed to access to the requested action; otherwise returns false. */ bool TAccessValidator::validate(const TAbstractUser *user) const { bool ret = allowDefault; const TActionController *controller = Tf::currentContext()->currentController(); Q_ASSERT(controller); if (accessRules.isEmpty()) { tWarn("No rule for access validation: %s", qPrintable(controller->className())); return ret; } if (!user || user->identityKey().isEmpty()) { // Searches a access rule for an unauthenticated user for (const auto &rule : accessRules) { if (rule.type == AccessRule::UnauthenticatedUser && rule.action == controller->activeAction()) { ret = rule.allow; break; } } if (ret) { tSystemDebug("Access '%s' action by an unauthenticated user : Allow", qPrintable(controller->activeAction())); } else { tSystemWarn("Access '%s' action by an unauthenticated user : Deny", qPrintable(controller->activeAction())); } } else { for (const auto &rule : accessRules) { if (rule.action == controller->activeAction() && ((rule.type == AccessRule::User && rule.key == user->identityKey()) || (!user->groupKey().isEmpty() && rule.key == user->groupKey()))) { ret = rule.allow; break; } } if (ret) { tSystemDebug("Access '%s' action by '%s' user : Allow", qPrintable(controller->activeAction()), qPrintable(user->identityKey())); } else { tSystemWarn("Access '%s' action by '%s' user : Deny", qPrintable(controller->activeAction()), qPrintable(user->identityKey())); } } return ret; }
/*! Renders a static error page with the status code, which page is [statusCode].html in the \a public directory. */ bool TActionController::renderErrorResponse(int statusCode) { bool ret = false; if (rendered) { tWarn("Has rendered already: %s", qPrintable(className() + '#' + activeAction())); return ret; } QString file = Tf::app()->publicPath() + QString::number(statusCode) + QLatin1String(".html"); if (QFileInfo(file).exists()) { ret = sendFile(file, "text/html", "", false); } else { response.setBody(""); } setStatusCode(statusCode); rendered = true; return ret; }
/*! \~english Renders the template given by \a templateName with the layout \a layout. \~japanese レイアウト \a layout を適用し、テンプレート \a templateName を描画する */ bool TActionController::renderTemplate(const QString &templateName, const QString &layout) { T_TRACEFUNC(""); if (rendered) { tWarn("Has rendered already: %s", qPrintable(className() + '#' + activeAction())); return false; } rendered = true; // Creates view-object and displays it QStringList names = templateName.split("/"); if (names.count() != 2) { tError("Invalid patameter: %s", qPrintable(templateName)); return false; } TDispatcher<TActionView> viewDispatcher(viewClassName(names[0], names[1])); setLayout(layout); response.setBody(renderView(viewDispatcher.object())); return (!response.isBodyNull()); }
qint64 THttpSocket::write(const THttpHeader *header, QIODevice *body) { T_TRACEFUNC(""); if (body && !body->isOpen()) { if (!body->open(QIODevice::ReadOnly)) { tWarn("open failed"); return -1; } } // Writes HTTP header QByteArray hdata = header->toByteArray(); qint64 total = writeRawData(hdata.data(), hdata.size()); if (total < 0) { return -1; } if (body) { QBuffer *buffer = qobject_cast<QBuffer *>(body); if (buffer) { if (writeRawData(buffer->data().data(), buffer->size()) != buffer->size()) { return -1; } total += buffer->size(); } else { QByteArray buf(WRITE_BUFFER_LENGTH, 0); qint64 readLen = 0; while ((readLen = body->read(buf.data(), buf.size())) > 0) { if (writeRawData(buf.data(), readLen) != readLen) { return -1; } total += readLen; } } } return total; }
void TActionContext::execute() { T_TRACEFUNC(""); THttpResponseHeader responseHeader; accessLogger.open(); try { if (!readRequest()) { return; } const THttpRequestHeader &hdr = httpReq->header(); // Access log accessLogger.setTimestamp(QDateTime::currentDateTime()); QByteArray firstLine = hdr.method() + ' ' + hdr.path(); firstLine += QString(" HTTP/%1.%2").arg(hdr.majorVersion()).arg(hdr.minorVersion()).toLatin1(); accessLogger.setRequest(firstLine); accessLogger.setRemoteHost( (Tf::app()->appSettings().value(LISTEN_PORT).toUInt() > 0) ? clientAddress().toString().toLatin1() : QByteArray("(unix)") ); tSystemDebug("method : %s", hdr.method().data()); tSystemDebug("path : %s", hdr.path().data()); Tf::HttpMethod method = httpReq->method(); QString path = THttpUtility::fromUrlEncoding(hdr.path().mid(0, hdr.path().indexOf('?'))); // Routing info exists? TRouting rt = TUrlRoute::instance().findRouting(method, path); tSystemDebug("Routing: controller:%s action:%s", rt.controller.data(), rt.action.data()); if (rt.isEmpty()) { // Default URL routing rt.params = path.split('/'); if (path.startsWith(QLatin1Char('/')) && !rt.params.isEmpty()) { rt.params.removeFirst(); // unuse first item } if (path.endsWith(QLatin1Char('/')) && !rt.params.isEmpty()) { rt.params.removeLast(); // unuse last item } // Direct view render mode? if (Tf::app()->appSettings().value(DIRECT_VIEW_RENDER_MODE).toBool()) { // Direct view setting rt.controller = "directcontroller"; rt.action = "show"; } else { if (!rt.params.value(0).isEmpty()) { rt.controller = rt.params.takeFirst().toLower().toLatin1() + "controller"; if (rt.controller == "applicationcontroller") { rt.controller.clear(); // Can not call 'ApplicationController' } // Default action: index rt.action = rt.params.value(0, QLatin1String("index")).toLatin1(); if (!rt.params.isEmpty()) { rt.params.takeFirst(); } } tSystemDebug("Active Controller : %s", rt.controller.data()); } } // Call controller method TDispatcher<TActionController> ctlrDispatcher(rt.controller); currController = ctlrDispatcher.object(); if (currController) { currController->setActionName(rt.action); // Session if (currController->sessionEnabled()) { TSession session; QByteArray sessionId = httpReq->cookie(TSession::sessionName()); if (!sessionId.isEmpty()) { // Finds a session session = TSessionManager::instance().findSession(sessionId); } currController->setSession(session); // Exports flash-variant currController->exportAllFlashVariants(); } // Verify authenticity token if (Tf::app()->appSettings().value(ENABLE_CSRF_PROTECTION_MODULE, true).toBool() && currController->csrfProtectionEnabled() && !currController->exceptionActionsOfCsrfProtection().contains(rt.action)) { if (method == Tf::Post || method == Tf::Put || method == Tf::Delete) { if (!currController->verifyRequest(*httpReq)) { throw SecurityException("Invalid authenticity token", __FILE__, __LINE__); } } } if (currController->sessionEnabled()) { if (currController->session().id().isEmpty() || Tf::app()->appSettings().value(AUTO_ID_REGENERATION).toBool()) { TSessionManager::instance().remove(currController->session().sessionId); // Removes the old session // Re-generate session ID currController->session().sessionId = TSessionManager::instance().generateId(); tSystemDebug("Re-generate session ID: %s", currController->session().sessionId.data()); } // Sets CSRF protection informaion TActionController::setCsrfProtectionInto(currController->session()); } // Database Transaction transactions.setEnabled(currController->transactionEnabled()); // Do filters if (currController->preFilter()) { // Dispathes bool dispatched = ctlrDispatcher.invoke(rt.action, rt.params); if (dispatched) { autoRemoveFiles << currController->autoRemoveFiles; // Adds auto-remove files // Post fileter currController->postFilter(); if (currController->rollbackRequested()) { rollbackTransactions(); } else { // Commits a transaction to the database commitTransactions(); } // Session store if (currController->sessionEnabled()) { bool stored = TSessionManager::instance().store(currController->session()); if (stored) { QDateTime expire; if (TSessionManager::sessionLifeTime() > 0) { expire = QDateTime::currentDateTime().addSecs(TSessionManager::sessionLifeTime()); } // Sets the path in the session cookie QString cookiePath = Tf::app()->appSettings().value(SESSION_COOKIE_PATH).toString(); currController->addCookie(TSession::sessionName(), currController->session().id(), expire, cookiePath); } } } } // Sets charset to the content-type QByteArray ctype = currController->response.header().contentType().toLower(); if (ctype.startsWith("text") && !ctype.contains("charset")) { ctype += "; charset="; ctype += Tf::app()->codecForHttpOutput()->name(); currController->response.header().setContentType(ctype); } // Sets the default status code of HTTP response accessLogger.setStatusCode( (!currController->response.isBodyNull()) ? currController->statusCode() : Tf::InternalServerError ); currController->response.header().setStatusLine(accessLogger.statusCode(), THttpUtility::getResponseReasonPhrase(accessLogger.statusCode())); // Writes a response and access log int bytes = writeResponse(currController->response.header(), currController->response.bodyIODevice(), currController->response.bodyLength()); accessLogger.setResponseBytes(bytes); // Session GC TSessionManager::instance().collectGarbage(); } else { accessLogger.setStatusCode( Tf::BadRequest ); if (method == Tf::Get) { // GET Method path.remove(0, 1); QFile reqPath(Tf::app()->publicPath() + path); QFileInfo fi(reqPath); if (fi.isFile() && fi.isReadable()) { // Check "If-Modified-Since" header for caching bool sendfile = true; QByteArray ifModifiedSince = hdr.rawHeader("If-Modified-Since"); if (!ifModifiedSince.isEmpty()) { QDateTime dt = THttpUtility::fromHttpDateTimeString(ifModifiedSince); sendfile = (!dt.isValid() || dt != fi.lastModified()); } if (sendfile) { // Sends a request file responseHeader.setRawHeader("Last-Modified", THttpUtility::toHttpDateTimeString(fi.lastModified())); QByteArray type = Tf::app()->internetMediaType(fi.suffix()); int bytes = writeResponse(Tf::OK, responseHeader, type, &reqPath, reqPath.size()); accessLogger.setResponseBytes( bytes ); } else { // Not send the data int bytes = writeResponse(Tf::NotModified, responseHeader); accessLogger.setResponseBytes( bytes ); } } else { int bytes = writeResponse(Tf::NotFound, responseHeader); accessLogger.setResponseBytes( bytes ); } accessLogger.setStatusCode( responseHeader.statusCode() ); } else if (method == Tf::Post) { // file upload? } else { // HEAD, DELETE, ... } } } catch (ClientErrorException &e) { tWarn("Caught ClientErrorException: status code:%d", e.statusCode()); int bytes = writeResponse(e.statusCode(), responseHeader); accessLogger.setResponseBytes( bytes ); accessLogger.setStatusCode( e.statusCode() ); } catch (SqlException &e) { tError("Caught SqlException: %s [%s:%d]", qPrintable(e.message()), qPrintable(e.fileName()), e.lineNumber()); tSystemError("Caught SqlException: %s [%s:%d]", qPrintable(e.message()), qPrintable(e.fileName()), e.lineNumber()); closeHttpSocket(); } catch (KvsException &e) { tError("Caught KvsException: %s [%s:%d]", qPrintable(e.message()), qPrintable(e.fileName()), e.lineNumber()); tSystemError("Caught KvsException: %s [%s:%d]", qPrintable(e.message()), qPrintable(e.fileName()), e.lineNumber()); closeHttpSocket(); } catch (SecurityException &e) { tError("Caught SecurityException: %s [%s:%d]", qPrintable(e.message()), qPrintable(e.fileName()), e.lineNumber()); tSystemError("Caught SecurityException: %s [%s:%d]", qPrintable(e.message()), qPrintable(e.fileName()), e.lineNumber()); closeHttpSocket(); } catch (RuntimeException &e) { tError("Caught RuntimeException: %s [%s:%d]", qPrintable(e.message()), qPrintable(e.fileName()), e.lineNumber()); tSystemError("Caught RuntimeException: %s [%s:%d]", qPrintable(e.message()), qPrintable(e.fileName()), e.lineNumber()); closeHttpSocket(); } catch (...) { tError("Caught Exception"); tSystemError("Caught Exception"); closeHttpSocket(); } // Push to the pool TActionContext::releaseSqlDatabases(); TActionContext::releaseKvsDatabases(); TActionContext::accessLogger.write(); // Writes access log releaseHttpSocket(); }
bool TSqlObject::update() { if (isNew()) { sqlError = QSqlError(QLatin1String("No record to update"), QString(), QSqlError::UnknownError); tWarn("Unable to update the '%s' object. Create it before!", metaObject()->className()); return false; } QString where(" WHERE "); int revIndex = metaObject()->indexOfProperty(REVISION_PROPERTY_NAME); if (revIndex >= 0) { bool ok; int oldRevision = property(REVISION_PROPERTY_NAME).toInt(&ok); if (!ok || oldRevision <= 0) { sqlError = QSqlError(QLatin1String("Unable to convert the 'revision' property to an int"), QString(), QSqlError::UnknownError); tError("Unable to convert the 'revision' property to an int, %s", qPrintable(objectName())); return false; } setProperty(REVISION_PROPERTY_NAME, oldRevision + 1); where.append(TSqlQuery::escapeIdentifier(REVISION_PROPERTY_NAME)); where.append("=").append(TSqlQuery::formatValue(oldRevision)); where.append(" AND "); } // Updates the value of 'updated_at' property for (int i = metaObject()->propertyOffset(); i < metaObject()->propertyCount(); ++i) { const char *propName = metaObject()->property(i).name(); if (QLatin1String("updated_at") == propName) { setProperty(propName, QDateTime::currentDateTime()); break; } } syncToSqlRecord(); QString upd = TActionContext::currentDatabase().driver()->sqlStatement(QSqlDriver::UpdateStatement, tableName(), *static_cast<QSqlRecord *>(this), false); if (upd.isEmpty()) { sqlError = QSqlError(QLatin1String("No Fields to update"), QString(), QSqlError::StatementError); return false; } const char *pkName = metaObject()->property(metaObject()->propertyOffset() + primaryKeyIndex()).name(); if (primaryKeyIndex() < 0 || !pkName) { QString msg = QString("Not found the primary key for table ") + tableName(); sqlError = QSqlError(msg, QString(), QSqlError::StatementError); tError("%s", qPrintable(msg)); return false; } where.append(TSqlQuery::escapeIdentifier(pkName)); where.append("=").append(TSqlQuery::formatValue(property(pkName))); upd.append(where); tSystemDebug("SQL statement: %s", qPrintable(upd)); QSqlQuery query(TActionContext::currentDatabase()); bool res = query.exec(upd); sqlError = query.lastError(); if (!res) { tSystemError("SQL update error: %s", qPrintable(sqlError.text())); return false; } // Optimistic lock check if (revIndex >= 0 && query.numRowsAffected() != 1) { QString msg = QString("Row was updated or deleted from table ") + tableName() + QLatin1String(" by another transaction"); sqlError = QSqlError(msg, QString(), QSqlError::UnknownError); throw SqlException(msg, __FILE__, __LINE__); } return true; }
int main(int argc, char *argv[]) { TWebApplication webapp(argc, argv); // Setup loggers tSetupSystemLogger(); tSetupLoggers(); qInstallMsgHandler(messageOutput); #if defined(Q_OS_UNIX) webapp.watchUnixSignal(SIGTERM); webapp.ignoreUnixSignal(SIGINT); // Setup signal handlers for SIGSEGV, SIGILL, SIGFPE, SIGABRT and SIGBUS setupFailureWriter(writeFailure); setupSignalHandler(); #endif // Sets codec QTextCodec *codec = QTextCodec::codecForName("UTF-8"); // default value QByteArray codecName = webapp.treefrogSettings().value("InternalEncoding").toByteArray().trimmed(); if (!codecName.isEmpty()) { QTextCodec *c = QTextCodec::codecForName(codecName); if (c) { codec = c; } else { tWarn("no codec matching the name could be found: %s", codecName.data()); } } QTextCodec::setCodecForTr(codec); QTextCodec::setCodecForLocale(codec); tSystemDebug("setCodecForTr: %s", codec->name().data()); if (!webapp.webRootExists()) { tSystemError("No such directory"); return 1; } tSystemDebug("Web Root: %s", qPrintable(webapp.webRootPath())); if (!webapp.settingsFileExists()) { tSystemError("Settings file not found"); return 2; } QHash<QString, QString> args = convertArgs(QCoreApplication::arguments()); TApplicationServer *server = new TApplicationServer(&webapp); QString arg = args.value("-s"); if (!arg.isEmpty()) { // Sets a listening socket descriptor int sd = arg.toInt(); if (sd > 0) { if (server->setSocketDescriptor(sd)) { tSystemDebug("Set socket descriptor: %d", sd); } else { tSystemError("Failed to set socket descriptor: %d", sd); return 3; } } else { tSystemError("Invalid socket descriptor: %d", sd); return 4; } } server->open(); return webapp.exec(); }
void TActionContext::execute() { T_TRACEFUNC(); try { httpSocket = new THttpSocket; if (!httpSocket->setSocketDescriptor(socketDesc)) { emitError(httpSocket->error()); delete httpSocket; httpSocket = 0; return; } else { socketDesc = 0; } while (!httpSocket->canReadRequest()) { if (stopped) { tSystemDebug("Detected stop request"); break; } // Check timeout if (!httpSocket->waitForReadyRead(10000)) { tWarn("Read timed out after 10 seconds."); break; } } if (!httpSocket->canReadRequest()) { httpSocket->abort(); delete httpSocket; httpSocket = 0; return; } THttpRequest httpRequest = httpSocket->read(); const THttpRequestHeader &hd = httpRequest.header(); // Access log QByteArray firstLine = hd.method() + ' ' + hd.path(); firstLine += QString(" HTTP/%1.%2").arg(hd.majorVersion()).arg(hd.minorVersion()).toLatin1(); TAccessLog accessLog(httpSocket->peerAddress().toString().toLatin1(), firstLine); tSystemDebug("method : %s", hd.method().data()); tSystemDebug("path : %s", hd.path().data()); Tf::HttpMethod method = httpRequest.method(); QString path = THttpUtility::fromUrlEncoding(hd.path().split('?').value(0)); // Routing info exists? Routing rt = TUrlRoute::instance().findRouting(method, path); tSystemDebug("Routing: controller:%s action:%s", rt.controller.data(), rt.action.data()); if (rt.isEmpty()) { // Default URL routing rt.params = path.split('/'); if (path.startsWith(QLatin1Char('/')) && !rt.params.isEmpty()) { rt.params.removeFirst(); // unuse first item } if (path.endsWith(QLatin1Char('/')) && !rt.params.isEmpty()) { rt.params.removeLast(); // unuse last item } // Direct view render mode? if (Tf::app()->treefrogSettings().value(DIRECT_VIEW_RENDER_MODE).toBool()) { // Direct view setting rt.controller = "directcontroller"; rt.action = "show"; QString tmp = rt.params.join("/"); rt.params.clear(); rt.params << tmp; } else { if (!rt.params.value(0).isEmpty()) { rt.controller = rt.params.takeFirst().toLower().toLatin1() + "controller"; if (!rt.params.value(0).isEmpty()) { rt.action = rt.params.takeFirst().toLatin1(); } } tSystemDebug("Active Controller : %s", rt.controller.data()); } } // Call controller method TDispatcher<TActionController> ctlrDispatcher(rt.controller); TActionController *controller = ctlrDispatcher.object(); if (controller) { controller->setActionName(rt.action); controller->setHttpRequest(httpRequest); openDatabase(); // Session if (controller->sessionEnabled()) { TSession session; QByteArray sessionId = httpRequest.cookie(TSession::sessionName()); if (!sessionId.isEmpty()) { session = TSessionManager::instance().find(sessionId); } controller->setSession(session); // Exports flash-variant controller->exportAllFlashVariants(); } // Verify authenticity token if (controller->forgeryProtectionEnabled() && !controller->protectFromForgeryExcept().contains(rt.action)) { if (method == Tf::Post || method == Tf::Put || method == Tf::Delete) { if (!controller->verifyRequest(httpRequest)) { throw SecurityException("Invalid authenticity token", __FILE__, __LINE__); } } } // Sets the default status code of HTTP response int statusCode = 404; // Not Found // Re-generate session ID if (controller->sessionEnabled() && (controller->session().id().isEmpty() || Tf::app()->treefrogSettings().value(AUTO_ID_REGENERATION).toBool())) { controller->session().regenerateId(); } if (controller->transactionEnabled()) { // Begins a transaction on the database beginTransaction(); } // Do filters if (controller->preFilter()) { // Dispathes bool dispatched = ctlrDispatcher.invoke(rt.action, rt.params); if (dispatched) { autoRemoveFiles = controller->autoRemoveFiles; // Set auto remove files // Post fileter controller->postFilter(); if (controller->transactionEnabled() && !controller->rollbackEnabled()) { // Commits a transaction to the database commitTransaction(); } // Session store if (controller->sessionEnabled()) { bool stored = TSessionManager::instance().store(controller->session()); if (stored) { controller->addCookie(TSession::sessionName(), controller->session().id(), QDateTime(), "/"); } } // Sets status code statusCode = (!controller->response.isBodyNull()) ? controller->statusCode() : 500 /* Internal Server Error */; } } controller->response.header().setStatusLine(statusCode, THttpUtility::getResponseReasonPhrase(statusCode)); accessLog.statusCode = statusCode; accessLog.responseBytes = writeResponse(controller->response.header(), controller->response.bodyIODevice(), controller->response.bodyLength()); // Outputs access log tAccessLog(accessLog); } if (!controller) { // GET Method? if (method == Tf::Get) { path.remove(0, 1); QFile reqPath(Tf::app()->publicPath() + path); QFileInfo fi(reqPath); if (fi.isFile() && fi.isReadable()) { QByteArray type = Tf::app()->internetMediaType(fi.suffix()); writeResponse(200, type, &reqPath, reqPath.size()); } else { writeResponse(404); // Not Found } } else if (method == Tf::Post) { // file upload? } else { // HEAD, DELETE, ... } } } catch (ClientErrorException &e) { tWarn("Caught ClientErrorException: status code:%d", e.statusCode()); writeResponse(e.statusCode()); } catch (SqlException &e) { tError("Caught SqlException: %s [%s:%d]", qPrintable(e.message()), qPrintable(e.fileName()), e.lineNumber()); } catch (SecurityException &e) { tError("Caught SecurityException: %s [%s:%d]", qPrintable(e.message()), qPrintable(e.fileName()), e.lineNumber()); } catch (RuntimeException &e) { tError("Caught RuntimeException: %s [%s:%d]", qPrintable(e.message()), qPrintable(e.fileName()), e.lineNumber()); } catch (...) { tError("Caught Exception"); } closeDatabase(); httpSocket->disconnectFromHost(); // Destorys the object in the thread which created it delete httpSocket; httpSocket = 0; }
/*! Deletes the record with this primary key from the database. */ bool TSqlObject::remove() { if (isNew()) { sqlError = QSqlError(QLatin1String("No record to remove"), QString(), QSqlError::UnknownError); tWarn("Unable to remove the '%s' object. Create it before!", metaObject()->className()); return false; } QSqlDatabase &database = Tf::currentSqlDatabase(databaseId()); QString del = database.driver()->sqlStatement(QSqlDriver::DeleteStatement, tableName(), *static_cast<QSqlRecord *>(this), false); if (del.isEmpty()) { sqlError = QSqlError(QLatin1String("Unable to delete row"), QString(), QSqlError::StatementError); return false; } del.append(" WHERE "); int revIndex = -1; for (int i = metaObject()->propertyOffset(); i < metaObject()->propertyCount(); ++i) { const char *propName = metaObject()->property(i).name(); QByteArray prop = QByteArray(propName).toLower(); if (prop == LockRevision) { bool ok; int revision = property(propName).toInt(&ok); if (!ok || revision <= 0) { sqlError = QSqlError(QLatin1String("Unable to convert the 'revision' property to an int"), QString(), QSqlError::UnknownError); tError("Unable to convert the 'revision' property to an int, %s", qPrintable(objectName())); return false; } del.append(QLatin1String(propName)); del.append("=").append(TSqlQuery::formatValue(revision, database)); del.append(" AND "); revIndex = i; break; } } const char *pkName = metaObject()->property(metaObject()->propertyOffset() + primaryKeyIndex()).name(); if (primaryKeyIndex() < 0 || !pkName) { QString msg = QString("Primary key not found for table ") + tableName() + QLatin1String(". Create a primary key!"); sqlError = QSqlError(msg, QString(), QSqlError::StatementError); tError("%s", qPrintable(msg)); return false; } del.append(QLatin1String(pkName)); del.append("=").append(TSqlQuery::formatValue(value(pkName), database)); TSqlQuery query(database); bool ret = query.exec(del); sqlError = query.lastError(); if (ret) { // Optimistic lock check if (query.numRowsAffected() != 1) { if (revIndex >= 0) { QString msg = QString("Row was updated or deleted from table ") + tableName() + QLatin1String(" by another transaction"); sqlError = QSqlError(msg, QString(), QSqlError::UnknownError); throw SqlException(msg, __FILE__, __LINE__); } tWarn("Row was deleted by another transaction, %s", qPrintable(tableName())); } clear(); } return ret; }
/*! Updates the corresponding record with the properties of the object. */ bool TSqlObject::update() { if (isNew()) { sqlError = QSqlError(QLatin1String("No record to update"), QString(), QSqlError::UnknownError); tWarn("Unable to update the '%s' object. Create it before!", metaObject()->className()); return false; } QSqlDatabase &database = Tf::currentSqlDatabase(databaseId()); QString where(" WHERE "); // Updates the value of 'updated_at' or 'modified_at' property bool updflag = false; int revIndex = -1; for (int i = metaObject()->propertyOffset(); i < metaObject()->propertyCount(); ++i) { const char *propName = metaObject()->property(i).name(); QByteArray prop = QByteArray(propName).toLower(); if (!updflag && (prop == UpdatedAt || prop == ModifiedAt)) { setProperty(propName, QDateTime::currentDateTime()); updflag = true; } else if (revIndex < 0 && prop == LockRevision) { bool ok; int oldRevision = property(propName).toInt(&ok); if (!ok || oldRevision <= 0) { sqlError = QSqlError(QLatin1String("Unable to convert the 'revision' property to an int"), QString(), QSqlError::UnknownError); tError("Unable to convert the 'revision' property to an int, %s", qPrintable(objectName())); return false; } setProperty(propName, oldRevision + 1); revIndex = i; where.append(QLatin1String(propName)); where.append("=").append(TSqlQuery::formatValue(oldRevision, database)); where.append(" AND "); } else { // continue } } QString upd; // UPDATE Statement upd.reserve(255); upd.append(QLatin1String("UPDATE ")).append(tableName()).append(QLatin1String(" SET ")); int pkidx = metaObject()->propertyOffset() + primaryKeyIndex(); const char *pkName = metaObject()->property(pkidx).name(); if (primaryKeyIndex() < 0 || !pkName) { QString msg = QString("Primary key not found for table ") + tableName() + QLatin1String(". Create a primary key!"); sqlError = QSqlError(msg, QString(), QSqlError::StatementError); tError("%s", qPrintable(msg)); return false; } QVariant origpkval = value(pkName); where.append(QLatin1String(pkName)); where.append("=").append(TSqlQuery::formatValue(origpkval, database)); // Restore the value of primary key QObject::setProperty(pkName, origpkval); for (int i = metaObject()->propertyOffset(); i < metaObject()->propertyCount(); ++i) { const char *propName = metaObject()->property(i).name(); QVariant newval = QObject::property(propName); QVariant recval = QSqlRecord::value(QLatin1String(propName)); if (i != pkidx && recval.isValid() && recval != newval) { upd.append(QLatin1String(propName)); upd.append(QLatin1Char('=')); upd.append(TSqlQuery::formatValue(newval, database)); upd.append(QLatin1String(", ")); } } if (!upd.endsWith(QLatin1String(", "))) { tSystemDebug("SQL UPDATE: Same values as that of the record. No need to update."); return true; } upd.chop(2); syncToSqlRecord(); upd.append(where); TSqlQuery query(database); bool ret = query.exec(upd); sqlError = query.lastError(); if (ret) { // Optimistic lock check if (revIndex >= 0 && query.numRowsAffected() != 1) { QString msg = QString("Row was updated or deleted from table ") + tableName() + QLatin1String(" by another transaction"); sqlError = QSqlError(msg, QString(), QSqlError::UnknownError); throw SqlException(msg, __FILE__, __LINE__); } } return ret; }
QString TPrototypeAjaxHelper::optionsToString(const TOption &options) const { QString string; // Adds authenticity_token TOption opt(options); QVariantMap map; QVariant v = opt[Tf::Parameters]; if (v.isValid() && v.canConvert(QVariant::Map)) { map = v.toMap(); } map.insert("authenticity_token", actionView()->authenticityToken()); opt.insert(Tf::Parameters, map); for (QMapIterator<int, QVariant> i(opt); i.hasNext(); ) { i.next(); // Appends ajax option QString s = stringOptionHash()->value(i.key()); if (!s.isEmpty() && i.value().canConvert(QVariant::String)) { string += s; string += QLatin1Char('\''); string += i.value().toString(); string += QLatin1String("', "); continue; } s = boolOptionHash()->value(i.key()); if (!s.isEmpty() && i.value().canConvert(QVariant::Bool)) { string += s; string += (i.value().toBool()) ? QLatin1String("true, ") : QLatin1String("false, "); continue; } if (i.key() == Tf::Method && i.value().canConvert(QVariant::Int)) { string += QLatin1String("method:'"); string += methodHash()->value(i.value().toInt()); string += QLatin1String("', "); continue; } // Appends 'parameters' option if (i.key() == Tf::Parameters) { QString val; if (i.value().canConvert(QVariant::Map)) { QVariantMap m = i.value().toMap(); for (QMapIterator<QString, QVariant> it(m); it.hasNext(); ) { it.next(); if (it.value().canConvert<TJavaScriptObject>()) { val += it.key(); val += QLatin1String(":"); val += it.value().value<TJavaScriptObject>().toString(); val += QLatin1String(", "); } else if (it.value().canConvert(QVariant::String)) { val += it.key(); val += QLatin1String(":'"); val += THttpUtility::toUrlEncoding(it.value().toString()); val += QLatin1String("', "); } } val.chop(2); } if (!val.isEmpty()) { string += QLatin1String("parameters: { "); string += val; string += QLatin1String(" }, "); } continue; } // Appends ajax callbacks s = eventStringHash()->value(i.key()); if (!s.isEmpty() && i.value().canConvert(QVariant::String)) { string += s; string += i.value().toString(); string += QLatin1String(", "); continue; } else { tWarn("invalid parameter: %d [%s:%d]", i.key(), __FILE__, __LINE__); } } string.chop(2); return string; }
void TWebSocketWorker::run() { QString es = TUrlRoute::splitPath(requestPath).value(0).toLower() + "endpoint"; TDispatcher<TWebSocketEndpoint> dispatcher(es); TWebSocketEndpoint *endpoint = dispatcher.object(); if (endpoint) { tSystemDebug("Found endpoint: %s", qPrintable(es)); tSystemDebug("TWebSocketWorker opcode: %d", opcode); switch (opcode) { case TWebSocketFrame::Continuation: // means session opening if (sessionStore.id().isEmpty()) { endpoint->onOpen(sessionStore); } else { tError("Invalid logic [%s:%d]", __FILE__, __LINE__); } break; case TWebSocketFrame::TextFrame: endpoint->onTextReceived(QString::fromUtf8(requestData)); break; case TWebSocketFrame::BinaryFrame: endpoint->onBinaryReceived(requestData); break; case TWebSocketFrame::Close: endpoint->onClose(); endpoint->closeWebSocket(); break; case TWebSocketFrame::Ping: endpoint->onPing(); endpoint->sendPong(); break; case TWebSocketFrame::Pong: endpoint->onPong(); break; default: tWarn("Invalid opcode: 0x%x [%s:%d]", (int)opcode, __FILE__, __LINE__); break; } // Sends payload for (QListIterator<QVariant> it(endpoint->payloadList); it.hasNext(); ) { const QVariant &var = it.next(); switch (var.type()) { case QVariant::String: socket->sendText(var.toString()); break; case QVariant::ByteArray: socket->sendBinary(var.toByteArray()); break; case QVariant::Int: { int opcode = var.toInt(); switch (opcode) { case TWebSocketFrame::Close: socket->disconnect(); break; case TWebSocketFrame::Ping: socket->sendPing(); break; case TWebSocketFrame::Pong: socket->sendPong(); break; default: tError("Invalid logic [%s:%d]", __FILE__, __LINE__); break; } break; } default: tError("Invalid logic [%s:%d]", __FILE__, __LINE__); break; } } } }