/*
* ResourceResolver.cpp
*
* Created on: Dec 13, 2017
* Author: frank
*/
#include "ResourceResolver.hpp"
namespace httpsserver {
ResourceResolver::ResourceResolver() {
_nodes = new std::vector();
_defaultNode = NULL;
}
ResourceResolver::~ResourceResolver() {
delete _nodes;
}
/**
* This method will register the HTTPSNode so it is reachable and its callback gets called for a request
*/
void ResourceResolver::registerNode(ResourceNode *node) {
_nodes->push_back(node);
}
/**
* This method can be used to deactivate a HTTPSNode that has been registered previously
*/
void ResourceResolver::unregisterNode(ResourceNode *node) {
}
void ResourceResolver::resolveNode(const std::string &method, const std::string &url, ResolvedResource &resolvedResource) {
// Reset the resource
resolvedResource.setMatchingNode(NULL);
resolvedResource.setParams(NULL);
// Memory management of this object will be performed by the ResolvedResource instance
ResourceParameters * params = new ResourceParameters();
// Split URL in resource name and request params. Request params start after an optional '?'
size_t reqparamIdx = url.find('?');
// If no '?' is contained in url, 0:npos will return the string as it is
std::string resourceName = url.substr(0, reqparamIdx);
// Set request params in params object if a '?' exists
if (reqparamIdx != std::string::npos) {
do {
// Drop the '?' or '&'
reqparamIdx += 1;
// Parameters are separated by '&'
size_t nextparamIdx = url.find('&', reqparamIdx);
// Get the "name=value" string
std::string param = url.substr(reqparamIdx, nextparamIdx - reqparamIdx);
// Find the position where the string has to be split
size_t nvSplitIdx = param.find('=');
// Use empty string if only name is set. /foo?bar&baz=1 will return "" for bar
std::string name = param.substr(0, nvSplitIdx);
std::string value = "";
if (nvSplitIdx != std::string::npos) {
// TODO: There may be url encoding in here.
value = param.substr(nvSplitIdx+1);
}
// Now we finally have name and value.
params->setRequestParameter(name, value);
// Update reqparamIdx
reqparamIdx = nextparamIdx;
} while(reqparamIdx != std::string::npos);
}
// Check whether a resource matches
for(std::vector::iterator node = _nodes->begin(); node != _nodes->end(); ++node) {
params->resetUrlParameters();
if ((*node)->_method == method) {
const std::string nodepath = ((*node)->_path);
if (!((*node)->hasUrlParameter())) {
std::string logstring = "[ ] Testing simple match on " + nodepath;
HTTPS_DLOG(logstring.c_str())
// Simple matching, the node does not contain any resource parameters
if (nodepath == resourceName) {
resolvedResource.setMatchingNode(*node);
HTTPS_DLOG("[ ] It's a match!")
break;
}
} else {
std::string logstring = "[ ] Testing parameter match on " + nodepath;
HTTPS_DLOG(logstring.c_str())
// Advanced matching, we need to align the /?/ parts.
bool didMatch = true;
size_t urlIdx = 0; // Pointer how far the input url is processed
size_t nodeIdx = 0; // Pointer how far the node url is processed
for (int pIdx = 0; didMatch && pIdx < (*node)->getUrlParamCount(); pIdx++) {
size_t pOffset = (*node)->getParamIdx(pIdx);
// First step: Check static part
size_t staticLength = pOffset-nodeIdx;
if (nodepath.substr(nodeIdx, staticLength).compare(resourceName.substr(urlIdx, staticLength))!=0) {
// static part did not match
didMatch = false;
HTTPS_DLOGHEX("[ ] No match on static part", pIdx)
} else {
// static part did match, increase pointers
nodeIdx += staticLength + 1; // +1 to take care of the '*' placeholder.
urlIdx += staticLength; // The pointer should now point to the begin of the static part
// Second step: Grab the parameter value
if (nodeIdx == nodepath.length()) {
// Easy case: parse until end of string
params->setUrlParameter(pIdx, resourceName.substr(urlIdx));
} else {
// parse until first char after the placeholder
char terminatorChar = nodepath[nodeIdx];
size_t terminatorPosition = resourceName.find(terminatorChar, urlIdx);
if (terminatorPosition != std::string::npos) {
// We actually found the terminator
size_t dynamicLength = terminatorPosition-urlIdx;
params->setUrlParameter(pIdx, resourceName.substr(urlIdx, dynamicLength));
urlIdx = urlIdx + dynamicLength;
} else {
// We did not find the terminator
didMatch = false;
HTTPS_DLOGHEX("[ ] No match on dynamic part", pIdx)
}
}
} // static part did match
} // placeholder loop
// If there is some final static part to process
if (didMatch && nodeIdx < nodepath.length()) {
size_t staticLength = nodepath.length()-nodeIdx;
if (nodepath.substr(nodeIdx, staticLength).compare(url.substr(urlIdx, staticLength))!=0) {
didMatch = false;
HTTPS_DLOG("[ ] No match, final static part did not match")
} else {
urlIdx += staticLength;
// If there is some string remaining in the url that did not match
if (urlIdx < resourceName.length()) {
didMatch = false;
HTTPS_DLOG("[ ] No match, url is longer than final static part")
}
}
}
// Every check worked, so the full url matches and the params are set
if (didMatch) {
resolvedResource.setMatchingNode(*node);
HTTPS_DLOG("[ ] It's a match!")
break;
}
} // static/dynamic url
} // method check
} // resource node for loop
// If the resource did not match, configure the default resource
if (!resolvedResource.didMatch() && _defaultNode != NULL) {
params->resetUrlParameters();
resolvedResource.setMatchingNode(_defaultNode);
}
// If resolving did work, set the params, otherwise delete them
if (resolvedResource.didMatch()) {
// The resolvedResource now takes care of memory management for the params
resolvedResource.setParams(params);
} else {
delete params;
}
}
void ResourceResolver::setDefaultNode(ResourceNode * defaultNode) {
_defaultNode = defaultNode;
}
}