blob: 63a0c4fca8ec5e5e306288b3f4f36543401901de [file] [log] [blame]
/**
* @author: Jeff Thompson
* See COPYING for copyright and distribution information.
*/
#include <stdexcept>
#include <sstream>
#include "Name.hpp"
using namespace std;
namespace ndn {
/**
* Write the value to result, escaping characters according to the NDN URI Scheme.
* This also adds "..." to a value with zero or more ".".
* @param value the buffer with the value to escape
* @param result the string stream to write to.
*/
static void toEscapedString(const vector<unsigned char> &value, ostringstream &result)
{
bool gotNonDot = false;
for (unsigned i = 0; i < value.size(); ++i) {
if (value[i] != 0x2e) {
gotNonDot = true;
break;
}
}
if (!gotNonDot) {
// Special case for component of zero or more periods. Add 3 periods.
result << "...";
for (unsigned int i = 0; i < value.size(); ++i)
result << ".";
}
else {
// In case we need to escape, set to upper case hex and save the previous flags.
ios::fmtflags saveFlags = result.flags(ios::hex | ios::uppercase);
for (unsigned int i = 0; i < value.size(); ++i) {
unsigned char x = value[i];
// Check for 0-9, A-Z, a-z, (+), (-), (.), (_)
if (x >= 0x30 && x <= 0x39 || x >= 0x41 && x <= 0x5a ||
x >= 0x61 && x <= 0x7a || x == 0x2b || x == 0x2d ||
x == 0x2e || x == 0x5f)
result << x;
else {
result << "%";
if (x < 16)
result << "0";
result << (unsigned int)x;
}
}
// Restore.
result.flags(saveFlags);
}
}
static const char *WHITESPACE_CHARS = " \n\r\t";
/**
* Modify str in place to erase whitespace on the left.
* @param str
*/
static inline void trimLeft(string &str)
{
size_t found = str.find_first_not_of(WHITESPACE_CHARS);
if (found != string::npos) {
if (found > 0)
str.erase(0, found);
}
else
// All whitespace
str.clear();
}
/**
* Modify str in place to erase whitespace on the right.
* @param str
*/
static inline void trimRight(string &str)
{
size_t found = str.find_last_not_of(WHITESPACE_CHARS);
if (found != string::npos) {
if (found + 1 < str.size())
str.erase(found + 1);
}
else
// All whitespace
str.clear();
}
/**
* Modify str in place to erase whitespace on the left and right.
* @param str
*/
static void trim(string &str)
{
trimLeft(str);
trimRight(str);
}
/**
* Convert the hex character to an integer from 0 to 15, or -1 if not a hex character.
* @param c
* @return
*/
static int fromHexChar(unsigned char c)
{
if (c >= '0' && c <= '9')
return (int)c - (int)'0';
else if (c >= 'A' && c <= 'F')
return (int)c - (int)'A' + 10;
else if (c >= 'a' && c <= 'f')
return (int)c - (int)'a' + 10;
else
return -1;
}
/**
* Return a copy of str, converting each escaped "%XX" to the char value.
* @param str
*/
static string unescape(const string &str)
{
ostringstream result;
for (unsigned int i = 0; i < str.size(); ++i) {
if (str[i] == '%' && i + 2 < str.size()) {
int hi = fromHexChar(str[i + 1]);
int lo = fromHexChar(str[i + 2]);
if (hi < 0 || lo < 0)
// Invalid hex characters, so just keep the escaped string.
result << str[i] << str[i + 1] << str[i + 2];
else
result << (unsigned char)(16 * hi + lo);
// Skip ahead past the escaped value.
i += 2;
}
else
// Just copy through.
result << str[i];
}
return result.str();
}
bool NameComponent::setFromEscapedString(const char *first, const char *last)
{
string trimmedString(first, last);
trim(trimmedString);
string component = unescape(trimmedString);
if (component.find_first_not_of(".") == string::npos) {
// Special case for component of only periods.
if (component.size() <= 2)
// Zero, one or two periods is illegal. Ignore this component.
return false;
else {
// Remove 3 periods.
value_.clear();
value_.insert(value_.begin(), component.begin() + 3, component.end());
}
}
else {
value_.clear();
value_.insert(value_.begin(), component.begin(), component.end());
}
return true;
}
Name::Name(const char *uri_cstr)
{
string uri = uri_cstr;
trim(uri);
if (uri.size() == 0)
return;
size_t iColon = uri.find(':');
if (iColon != string::npos) {
// Make sure the colon came before a '/'.
size_t iFirstSlash = uri.find('/');
if (iFirstSlash == string::npos || iColon < iFirstSlash) {
// Omit the leading protocol such as ndn:
uri.erase(0, iColon + 1);
trim(uri);
}
}
// Trim the leading slash and possibly the authority.
if (uri[0] == '/') {
if (uri.size() >= 2 && uri[1] == '/') {
// Strip the authority following "//".
size_t iAfterAuthority = uri.find('/', 2);
if (iAfterAuthority == string::npos)
// Unusual case: there was only an authority.
return;
else {
uri.erase(0, iAfterAuthority + 1);
trim(uri);
}
}
else {
uri.erase(0, 1);
trim(uri);
}
}
size_t iComponentStart = 0;
// Unescape the components.
while (iComponentStart < uri.size()) {
size_t iComponentEnd = uri.find("/", iComponentStart);
if (iComponentEnd == string::npos)
iComponentEnd = uri.size();
components_.push_back(NameComponent());
if (!components_[components_.size() - 1].setFromEscapedString(&uri[iComponentStart], &uri[iComponentEnd]))
// Ignore the illegal component. This also gets rid of a trailing '/'.
components_.pop_back();
iComponentStart = iComponentEnd + 1;
}
}
void Name::get(struct ndn_Name &nameStruct) const
{
if (nameStruct.maxComponents < components_.size())
throw runtime_error("nameStruct.maxComponents must be >= this name getNComponents()");
nameStruct.nComponents = components_.size();
for (unsigned int i = 0; i < nameStruct.nComponents; ++i)
components_[i].get(nameStruct.components[i]);
}
void Name::set(const struct ndn_Name &nameStruct)
{
clear();
for (unsigned int i = 0; i < nameStruct.nComponents; ++i)
addComponent(nameStruct.components[i].value, nameStruct.components[i].valueLength);
}
std::string Name::to_uri() const
{
if (components_.size() == 0)
return "/";
ostringstream result;
for (unsigned int i = 0; i < components_.size(); ++i) {
result << "/";
toEscapedString(components_[i].getValue(), result);
}
return result.str();
}
}