Пример #1
0
void HTTPDigestCredentials::updateAuthParams(const HTTPRequest& request)
{
	MD5Engine engine;
	const std::string& qop = _requestAuthParams.get(QOP_PARAM, DEFAULT_QOP);
	const std::string& realm = _requestAuthParams.getRealm();
	const std::string& nonce = _requestAuthParams.get(NONCE_PARAM);

	_requestAuthParams.set(URI_PARAM, request.getURI());

	if (qop.empty())
	{
		const std::string ha1 = digest(engine, _username, realm, _password);
		const std::string ha2 = digest(engine, request.getMethod(), request.getURI());

		_requestAuthParams.set(RESPONSE_PARAM, digest(engine, ha1, nonce, ha2));
	}
	else if (icompare(qop, AUTH_PARAM) == 0) 
	{
		const std::string& cnonce = _requestAuthParams.get(CNONCE_PARAM);

		const std::string ha1 = digest(engine, _username, realm, _password);
		const std::string ha2 = digest(engine, request.getMethod(), request.getURI());
		const std::string nc = formatNonceCounter(updateNonceCounter(nonce));

		_requestAuthParams.set(NC_PARAM, nc);
		_requestAuthParams.set(RESPONSE_PARAM, digest(engine, ha1, nonce, nc, cnonce, qop, ha2));
	}
}
Пример #2
0
void HTMLForm::load(const HTTPRequest& request, std::istream& requestBody, PartHandler& handler)
{
	if (request.getMethod() == HTTPRequest::HTTP_POST)
	{
		std::string mediaType;
		NameValueCollection params;
		MessageHeader::splitParameters(request.getContentType(), mediaType, params); 
		_encoding = mediaType;
		if (_encoding == ENCODING_MULTIPART)
		{
			_boundary = params["boundary"];
			readMultipart(requestBody, handler);
		}
		else
		{
			readUrl(requestBody);
		}
	}
	else
	{
		URI uri(request.getURI());
		std::istringstream istr(uri.getRawQuery());
		readUrl(istr);
	}
}
Пример #3
0
void HTTPRequestTest::testRead1()
{
	std::string s("GET / HTTP/1.0\r\n\r\n");
	std::istringstream istr(s);
	HTTPRequest request;
	request.read(istr);
	assert (request.getMethod() == HTTPRequest::HTTP_GET);
	assert (request.getURI() == "/");
	assert (request.getVersion() == HTTPMessage::HTTP_1_0);
	assert (request.empty());
	assert (istr.get() == -1);
}
Пример #4
0
TEST(HTTPRequest, testRead1)
{
	std::string s("GET / HTTP/1.0\r\n\r\n");
	std::istringstream istr(s);
	HTTPRequest request;
	request.read(istr);
	EXPECT_TRUE (request.getMethod() == HTTPRequest::HTTP_GET);
	EXPECT_TRUE (request.getURI() == "/");
	EXPECT_TRUE (request.getVersion() == HTTPMessage::HTTP_1_0);
	EXPECT_TRUE (request.empty());
	EXPECT_TRUE (istr.get() == -1);
}
Пример #5
0
bool HTTPDigestCredentials::verifyAuthParams(const HTTPRequest& request, const HTTPAuthenticationParams& params) const
{
	const std::string& nonce = params.get(NONCE_PARAM);
	const std::string& realm = params.getRealm();
	const std::string& qop   = params.get(QOP_PARAM, DEFAULT_QOP);
	std::string response;
	MD5Engine engine;
	if (qop.empty())
	{
		const std::string ha1 = digest(engine, _username, realm, _password);
		const std::string ha2 = digest(engine, request.getMethod(), request.getURI());
		response = digest(engine, ha1, nonce, ha2);
	}
	else if (icompare(qop, AUTH_PARAM) == 0) 
	{
		const std::string& cnonce = params.get(CNONCE_PARAM);
		const std::string& nc = params.get(NC_PARAM);
		const std::string ha1 = digest(engine, _username, realm, _password);
		const std::string ha2 = digest(engine, request.getMethod(), request.getURI());
		response = digest(engine, ha1, nonce, nc, cnonce, qop, ha2);
	}
	return response == params.get(RESPONSE_PARAM);
}
Пример #6
0
void HTTPRequestTest::testRead2()
{
	std::string s("HEAD /index.html HTTP/1.1\r\nConnection: Keep-Alive\r\nHost: localhost\r\nUser-Agent: Poco\r\n\r\n");
	std::istringstream istr(s);
	HTTPRequest request;
	request.read(istr);
	assert (request.getMethod() == HTTPRequest::HTTP_HEAD);
	assert (request.getURI() == "/index.html");
	assert (request.getVersion() == HTTPMessage::HTTP_1_1);
	assert (request.size() == 3);
	assert (request["Connection"] == "Keep-Alive");
	assert (request["Host"] == "localhost");
	assert (request["User-Agent"] == "Poco");
	assert (istr.get() == -1);
}
Пример #7
0
void HTTPRequestTest::testRead4()
{
	std::string s("POST /test.cgi HTTP/1.1\r\nConnection: Close\r\nContent-Length:   100  \r\nContent-Type: text/plain\r\nHost: localhost:8000\r\nUser-Agent: Poco\r\n\r\n");
	std::istringstream istr(s);
	HTTPRequest request;
	request.read(istr);
	assert (request.getMethod() == HTTPRequest::HTTP_POST);
	assert (request.getURI() == "/test.cgi");
	assert (request.getVersion() == HTTPMessage::HTTP_1_1);
	assert (request.size() == 5);
	assert (request["Connection"] == "Close");
	assert (request["Host"] == "localhost:8000");
	assert (request["User-Agent"] == "Poco");
	assert (request.getContentType() == "text/plain");
	assert (request.getContentLength() == 100);
	assert (istr.get() == -1);
}
Пример #8
0
std::string FileServerRequestHandler::getRequestPathname(const HTTPRequest& request)
{
    Poco::URI requestUri(request.getURI());
    // avoid .'s and ..'s
    requestUri.normalize();

    std::string path(requestUri.getPath());
    Poco::RegularExpression gitHashRe("/([0-9a-f]+)/");
    std::string gitHash;
    if (gitHashRe.extract(path, gitHash))
    {
        // Convert version back to a real file name.
        Poco::replaceInPlace(path, std::string("/loleaflet" + gitHash), std::string("/loleaflet/dist/"));
    }

    return path;
}
Пример #9
0
void HTMLForm::prepareSubmit(HTTPRequest& request)
{
	if (request.getMethod() == HTTPRequest::HTTP_POST)
	{
		if (_encoding == ENCODING_URL)
		{
			request.setContentType(_encoding);
			request.setChunkedTransferEncoding(false);
			Poco::CountingOutputStream ostr;
			writeUrl(ostr);
			request.setContentLength(ostr.chars());
		}
		else
		{
			_boundary = MultipartWriter::createBoundary();
			std::string ct(_encoding);
			ct.append("; boundary=\"");
			ct.append(_boundary);
			ct.append("\"");
			request.setContentType(ct);
		}
		if (request.getVersion() == HTTPMessage::HTTP_1_0)
		{
			request.setKeepAlive(false);
			request.setChunkedTransferEncoding(false);
		}
		else if (_encoding != ENCODING_URL)
		{
			request.setChunkedTransferEncoding(true);
		}
	}
	else
	{
		std::string uri = request.getURI();
		std::ostringstream ostr;
		writeUrl(ostr);
		uri.append("?");
		uri.append(ostr.str());
		request.setURI(uri);
	}
}
Пример #10
0
void FileServerRequestHandler::preprocessFile(const HTTPRequest& request, Poco::MemoryInputStream& message, const std::shared_ptr<StreamSocket>& socket)
{
    const auto host = ((LOOLWSD::isSSLEnabled() || LOOLWSD::isSSLTermination()) ? "wss://" : "ws://") + (LOOLWSD::ServerName.empty() ? request.getHost() : LOOLWSD::ServerName);
    const Poco::URI::QueryParameters params = Poco::URI(request.getURI()).getQueryParameters();

    // Is this a file we read at startup - if not; its not for serving.
    const std::string relPath = getRequestPathname(request);
    LOG_DBG("Preprocessing file: " << relPath);
    std::string preprocess = *getUncompressedFile(relPath);

    HTMLForm form(request, message);
    const std::string accessToken = form.get("access_token", "");
    const std::string accessTokenTtl = form.get("access_token_ttl", "");
    LOG_TRC("access_token=" << accessToken << ", access_token_ttl=" << accessTokenTtl);
    const std::string accessHeader = form.get("access_header", "");
    LOG_TRC("access_header=" << accessHeader);

    // Escape bad characters in access token.
    // This is placed directly in javascript in loleaflet.html, we need to make sure
    // that no one can do anything nasty with their clever inputs.
    std::string escapedAccessToken, escapedAccessHeader;
    Poco::URI::encode(accessToken, "'", escapedAccessToken);
    Poco::URI::encode(accessHeader, "'", escapedAccessHeader);

    unsigned long tokenTtl = 0;
    if (!accessToken.empty())
    {
        if (!accessTokenTtl.empty())
        {
            try
            {
                tokenTtl = std::stoul(accessTokenTtl);
            }
            catch (const std::exception& exc)
            {
                LOG_ERR("access_token_ttl must be represented as the number of milliseconds since January 1, 1970 UTC, when the token will expire");
            }
        }
        else
        {
            LOG_INF("WOPI host did not pass optional access_token_ttl");
        }
    }

    Poco::replaceInPlace(preprocess, std::string("%ACCESS_TOKEN%"), escapedAccessToken);
    Poco::replaceInPlace(preprocess, std::string("%ACCESS_TOKEN_TTL%"), std::to_string(tokenTtl));
    Poco::replaceInPlace(preprocess, std::string("%ACCESS_HEADER%"), escapedAccessHeader);
    Poco::replaceInPlace(preprocess, std::string("%HOST%"), host);
    Poco::replaceInPlace(preprocess, std::string("%VERSION%"), std::string(LOOLWSD_VERSION_HASH));
    Poco::replaceInPlace(preprocess, std::string("%SERVICE_ROOT%"), LOOLWSD::ServiceRoot);

    static const std::string linkCSS("<link rel=\"stylesheet\" href=\"%s/loleaflet/" LOOLWSD_VERSION_HASH "/%s.css\">");
    static const std::string scriptJS("<script src=\"%s/loleaflet/" LOOLWSD_VERSION_HASH "/%s.js\"></script>");

    std::string brandCSS(Poco::format(linkCSS, LOOLWSD::ServiceRoot, std::string(BRANDING)));
    std::string brandJS(Poco::format(scriptJS, LOOLWSD::ServiceRoot, std::string(BRANDING)));

    const auto& config = Application::instance().config();
#if ENABLE_SUPPORT_KEY
    const std::string keyString = config.getString("support_key", "");
    SupportKey key(keyString);
    if (!key.verify() || key.validDaysRemaining() <= 0)
    {
        brandCSS = Poco::format(linkCSS, LOOLWSD::ServiceRoot, std::string(BRANDING_UNSUPPORTED));
        brandJS = Poco::format(scriptJS, LOOLWSD::ServiceRoot, std::string(BRANDING_UNSUPPORTED));
    }
#endif

    Poco::replaceInPlace(preprocess, std::string("<!--%BRANDING_CSS%-->"), brandCSS);
    Poco::replaceInPlace(preprocess, std::string("<!--%BRANDING_JS%-->"), brandJS);

    // Customization related to document signing.
    std::string documentSigningDiv;
    const std::string documentSigningURL = config.getString("per_document.document_signing_url", "");
    if (!documentSigningURL.empty())
    {
        documentSigningDiv = "<div id=\"document-signing-bar\"></div>";
    }
    Poco::replaceInPlace(preprocess, std::string("<!--%DOCUMENT_SIGNING_DIV%-->"), documentSigningDiv);
    Poco::replaceInPlace(preprocess, std::string("%DOCUMENT_SIGNING_URL%"), documentSigningURL);

    const auto loleafletLogging = config.getString("loleaflet_logging", "false");
    Poco::replaceInPlace(preprocess, std::string("%LOLEAFLET_LOGGING%"), loleafletLogging);
    const std::string outOfFocusTimeoutSecs= config.getString("per_view.out_of_focus_timeout_secs", "60");
    Poco::replaceInPlace(preprocess, std::string("%OUT_OF_FOCUS_TIMEOUT_SECS%"), outOfFocusTimeoutSecs);
    const std::string idleTimeoutSecs= config.getString("per_view.idle_timeout_secs", "900");
    Poco::replaceInPlace(preprocess, std::string("%IDLE_TIMEOUT_SECS%"), idleTimeoutSecs);

    const std::string mimeType = "text/html";

    std::ostringstream oss;
    oss << "HTTP/1.1 200 OK\r\n"
        << "Date: " << Poco::DateTimeFormatter::format(Poco::Timestamp(), Poco::DateTimeFormat::HTTP_FORMAT) << "\r\n"
        << "Last-Modified: " << Poco::DateTimeFormatter::format(Poco::Timestamp(), Poco::DateTimeFormat::HTTP_FORMAT) << "\r\n"
        << "User-Agent: " << WOPI_AGENT_STRING << "\r\n"
        << "Cache-Control:max-age=11059200\r\n"
        << "ETag: \"" LOOLWSD_VERSION_HASH "\"\r\n"
        << "Content-Length: " << preprocess.size() << "\r\n"
        << "Content-Type: " << mimeType << "\r\n"
        << "X-Content-Type-Options: nosniff\r\n"
        << "X-XSS-Protection: 1; mode=block\r\n"
        << "Referrer-Policy: no-referrer\r\n";

    // Document signing: if endpoint URL is configured, whitelist that for
    // iframe purposes.
    std::ostringstream cspOss;
    cspOss << "Content-Security-Policy: default-src 'none'; "
           << "frame-src 'self' blob: " << documentSigningURL << "; "
           << "connect-src 'self' " << host << "; "
           << "script-src 'unsafe-inline' 'self'; "
           << "style-src 'self' 'unsafe-inline'; "
           << "font-src 'self' data:; "
           << "object-src blob:; ";

    // Frame ancestors: Allow loolwsd host, wopi host and anything configured.
    std::string configFrameAncestor = config.getString("net.frame_ancestors", "");
    std::string frameAncestors = configFrameAncestor;
    Poco::URI uriHost(host);
    if (uriHost.getHost() != configFrameAncestor)
        frameAncestors += " " + uriHost.getHost() + ":*";

    for (const auto& param : params)
    {
        if (param.first == "WOPISrc")
        {
            std::string wopiFrameAncestor;
            Poco::URI::decode(param.second, wopiFrameAncestor);
            Poco::URI uriWopiFrameAncestor(wopiFrameAncestor);
            // Remove parameters from URL
            wopiFrameAncestor = uriWopiFrameAncestor.getHost();
            if (wopiFrameAncestor != uriHost.getHost() && wopiFrameAncestor != configFrameAncestor)
            {
                frameAncestors += " " + wopiFrameAncestor + ":*";
                LOG_TRC("Picking frame ancestor from WOPISrc: " << wopiFrameAncestor);
            }
            break;
        }
    }

    if (!frameAncestors.empty())
    {
        LOG_TRC("Allowed frame ancestors: " << frameAncestors);
        // X-Frame-Options supports only one ancestor, ignore that
        //(it's deprecated anyway and CSP works in all major browsers)
        cspOss << "img-src 'self' data: " << frameAncestors << "; "
                << "frame-ancestors " << frameAncestors;
    }
    else
    {
        LOG_TRC("Denied all frame ancestors");
        cspOss << "img-src 'self' data: none;";
    }
    cspOss << "\r\n";
    // Append CSP to response headers too
    oss << cspOss.str();

    // Setup HTTP Public key pinning
    if ((LOOLWSD::isSSLEnabled() || LOOLWSD::isSSLTermination()) && config.getBool("ssl.hpkp[@enable]", false))
    {
        size_t i = 0;
        std::string pinPath = "ssl.hpkp.pins.pin[" + std::to_string(i) + "]";
        std::ostringstream hpkpOss;
        bool keysPinned = false;
        while (config.has(pinPath))
        {
            const std::string pin = config.getString(pinPath, "");
            if (!pin.empty())
            {
                hpkpOss << "pin-sha256=\"" << pin << "\"; ";
                keysPinned = true;
            }
            pinPath = "ssl.hpkp.pins.pin[" + std::to_string(++i) + "]";
        }

        if (keysPinned && config.getBool("ssl.hpkp.max_age[@enable]", false))
        {
            int maxAge = 1000; // seconds
            try
            {
                maxAge = config.getInt("ssl.hpkp.max_age", maxAge);
            }
            catch (Poco::SyntaxException& exc)
            {
                LOG_WRN("Invalid value of HPKP's max-age directive found in config file. Defaulting to "
                        << maxAge);
            }
            hpkpOss << "max-age=" << maxAge << "; ";
        }

        if (keysPinned && config.getBool("ssl.hpkp.report_uri[@enable]", false))
        {
            const std::string reportUri = config.getString("ssl.hpkp.report_uri", "");
            if (!reportUri.empty())
            {
                hpkpOss << "report-uri=" << reportUri << "; ";
            }
        }

        if (!hpkpOss.str().empty())
        {
            if (config.getBool("ssl.hpkp[@report_only]", false))
            {
                // Only send validation failure reports to reportUri while still allowing UAs to
                // connect to the server
                oss << "Public-Key-Pins-Report-Only: " << hpkpOss.str() << "\r\n";
            }
            else
            {
                oss << "Public-Key-Pins: " << hpkpOss.str() << "\r\n";
            }
        }
    }

    oss << "\r\n"
        << preprocess;

    socket->send(oss.str());
    LOG_DBG("Sent file: " << relPath << ": " << preprocess);
}
Пример #11
0
void FileServerRequestHandler::handleRequest(const HTTPRequest& request, Poco::MemoryInputStream& message,
                                             const std::shared_ptr<StreamSocket>& socket)
{
    try
    {
        bool noCache = false;
#if ENABLE_DEBUG
        noCache = true;
#endif
        Poco::Net::HTTPResponse response;
        Poco::URI requestUri(request.getURI());
        LOG_TRC("Fileserver request: " << requestUri.toString());
        requestUri.normalize(); // avoid .'s and ..'s

        std::string path(requestUri.getPath());
        if (path.find("loleaflet/" LOOLWSD_VERSION_HASH "/") == std::string::npos)
        {
            LOG_WRN("client - server version mismatch, disabling browser cache.");
            noCache = true;
        }

        std::vector<std::string> requestSegments;
        requestUri.getPathSegments(requestSegments);
        const std::string relPath = getRequestPathname(request);
        // Is this a file we read at startup - if not; its not for serving.
        if (requestSegments.size() < 1 || FileHash.find(relPath) == FileHash.end())
            throw Poco::FileNotFoundException("Invalid URI request: [" + requestUri.toString() + "].");

        const auto& config = Application::instance().config();
        const std::string loleafletHtml = config.getString("loleaflet_html", "loleaflet.html");
        const std::string endPoint = requestSegments[requestSegments.size() - 1];
        if (endPoint == loleafletHtml)
        {
            preprocessFile(request, message, socket);
            return;
        }

        if (request.getMethod() == HTTPRequest::HTTP_GET)
        {
            if (endPoint == "admin.html" ||
                endPoint == "adminSettings.html" ||
                endPoint == "adminHistory.html" ||
                endPoint == "adminAnalytics.html")
            {
                preprocessAdminFile(request, socket);
                return;
            }

            if (endPoint == "admin-bundle.js" ||
                endPoint == "admin-localizations.js")
            {
                noCache = true;

                if (!LOOLWSD::AdminEnabled)
                    throw Poco::FileAccessDeniedException("Admin console disabled");

                if (!FileServerRequestHandler::isAdminLoggedIn(request, response))
                    throw Poco::Net::NotAuthenticatedException("Invalid admin login");

                // Ask UAs to block if they detect any XSS attempt
                response.add("X-XSS-Protection", "1; mode=block");
                // No referrer-policy
                response.add("Referrer-Policy", "no-referrer");
            }

            // Do we have an extension.
            const std::size_t extPoint = endPoint.find_last_of('.');
            if (extPoint == std::string::npos)
                throw Poco::FileNotFoundException("Invalid file.");

            const std::string fileType = endPoint.substr(extPoint + 1);
            std::string mimeType;
            if (fileType == "js")
                mimeType = "application/javascript";
            else if (fileType == "css")
                mimeType = "text/css";
            else if (fileType == "html")
                mimeType = "text/html";
            else if (fileType == "png")
                mimeType = "image/png";
            else if (fileType == "svg")
                mimeType = "image/svg+xml";
            else
                mimeType = "text/plain";

            auto it = request.find("If-None-Match");
            if (it != request.end())
            {
                // if ETags match avoid re-sending the file.
                if (!noCache && it->second == "\"" LOOLWSD_VERSION_HASH "\"")
                {
                    // TESTME: harder ... - do we even want ETag support ?
                    std::ostringstream oss;
                    Poco::DateTime now;
                    Poco::DateTime later(now.utcTime(), int64_t(1000)*1000 * 60 * 60 * 24 * 128);
                    oss << "HTTP/1.1 304 Not Modified\r\n"
                        << "Date: " << Poco::DateTimeFormatter::format(
                            now, Poco::DateTimeFormat::HTTP_FORMAT) << "\r\n"
                        << "Expires: " << Poco::DateTimeFormatter::format(
                            later, Poco::DateTimeFormat::HTTP_FORMAT) << "\r\n"
                        << "User-Agent: " << WOPI_AGENT_STRING << "\r\n"
                        << "Cache-Control: max-age=11059200\r\n"
                        << "\r\n";
                    socket->send(oss.str());
                    socket->shutdown();
                    return;
                }
            }

            response.set("User-Agent", HTTP_AGENT_STRING);
            response.set("Date", Poco::DateTimeFormatter::format(Poco::Timestamp(), Poco::DateTimeFormat::HTTP_FORMAT));

            bool gzip = request.hasToken("Accept-Encoding", "gzip");
            const std::string *content;
#if ENABLE_DEBUG
            if (std::getenv("LOOL_SERVE_FROM_FS"))
            {
                // Useful to not serve from memory sometimes especially during loleaflet development
                // Avoids having to restart loolwsd everytime you make a change in loleaflet
                const std::string filePath = Poco::Path(LOOLWSD::FileServerRoot, relPath).absolute().toString();
                HttpHelper::sendFile(socket, filePath, mimeType, response, noCache);
                return;
            }
#endif
            if (gzip)
            {
                response.set("Content-Encoding", "gzip");
                content = getCompressedFile(relPath);
            }
            else
                content = getUncompressedFile(relPath);

            if (!noCache)
            {
                // 60 * 60 * 24 * 128 (days) = 11059200
                response.set("Cache-Control", "max-age=11059200");
                response.set("ETag", "\"" LOOLWSD_VERSION_HASH "\"");
            }
            response.setContentType(mimeType);
            response.add("X-Content-Type-Options", "nosniff");

            std::ostringstream oss;
            response.write(oss);
            const std::string header = oss.str();
            LOG_TRC("#" << socket->getFD() << ": Sending " <<
                    (!gzip ? "un":"") << "compressed : file [" << relPath << "]: " << header);
            socket->send(header);
            socket->send(*content);
        }
    }
    catch (const Poco::Net::NotAuthenticatedException& exc)
    {
        LOG_ERR("FileServerRequestHandler::NotAuthenticated: " << exc.displayText());
        sendError(401, request, socket, "", "", "WWW-authenticate: Basic realm=\"online\"\r\n");
    }
    catch (const Poco::FileAccessDeniedException& exc)
    {
        LOG_ERR("FileServerRequestHandler: " << exc.displayText());
        sendError(403, request, socket, "403 - Access denied!",
                  "You are unable to access");
    }
    catch (const Poco::FileNotFoundException& exc)
    {
        LOG_WRN("FileServerRequestHandler: " << exc.displayText());
        sendError(404, request, socket, "404 - file not found!",
                  "There seems to be a problem locating");
    }
}