/*
** Copyright 2002-2007 by LivingLogic AG, Bayreuth, Germany.
** Copyright 2002-2007 by Walter Dörwald
**
** All Rights Reserved
**
** See __init__.py for the license
*/
#include "Python.h"
static char escape__doc__[] =
"escape(string, safe) -> string\n\
\n\
Escape any character not in safe with a %xx sequence.\n\
If safe is not specified all 7bit characters are considered safe.";
static char hexdigits[16] = "0123456789abcdef";
static PyObject *escape(PyObject *self, PyObject *args)
{
PyObject *str;
PyObject *uni;
char *safe = NULL;
PyObject *res;
unsigned char *s;
unsigned char *starts;
unsigned char *ends;
char *r;
char *startr;
char *endr;
int newsize;
if (!PyArg_ParseTuple(args, "O|s:escape", &str, &safe))
return NULL;
uni = PyUnicode_FromObject(str);
if (!uni)
return NULL;
str = PyUnicode_EncodeUTF8(PyUnicode_AS_UNICODE(uni), PyUnicode_GET_SIZE(uni), NULL);
if (!str)
{
Py_DECREF(uni);
return NULL;
}
starts = PyString_AS_STRING(str);
ends = starts + PyString_GET_SIZE(str);
for (newsize = 0, s = starts; s < ends; ++s)
{
if (safe ? (strchr(safe, *s)!=NULL) : (*s<0x80))
++newsize;
else
newsize += 3;
}
res = PyString_FromStringAndSize(NULL, newsize);
if (res)
{
startr = PyString_AS_STRING(res);
endr = startr + PyString_GET_SIZE(res);
for (s = starts, r = startr; s < ends;)
{
if (safe ? (strchr(safe, *s)!=NULL) : (*s<0x80))
*r++ = *s++;
else
{
*r++ = '%';
*r++ = hexdigits[((*s)>>4) & 0xf];
*r++ = hexdigits[(*s++) & 0xf];
}
}
}
Py_DECREF(str);
Py_DECREF(uni);
return res;
}
static void widechar_to_utf8(unsigned long widechar, char **out)
{
unsigned long first_bits = 0;
int trail = 0;
if (widechar >= 0x80)
{
if (widechar < 0x00000800)
{
first_bits = 0xc0;
trail = 1;
}
else if (widechar < 0x00010000)
{
first_bits = 0xe0;
trail = 2;
}
}
{
int i;
for (i = trail; i; --i)
{
(*out)[i] = (char)((widechar & 0x3f) | 0x80);
widechar >>= 6;
}
(*out)[0] = (char) (widechar | first_bits);
}
*out += trail + 1;
}
static char unescape__doc__[] =
"unescape(string) -> unicode\n\
\n\
Unescape a %-escaped string. The result will be UTF-8 decoded if possible.\n\
If this fails, ISO-8859-1 will be tried.";
static PyObject *unescape(PyObject *self, PyObject *args)
{
char *in;
int len;
PyObject *res;
int pos = 0;
char *start;
char *out;
PyObject *uni;
if (!PyArg_ParseTuple(args, "s#:unescape", &in, &len))
return NULL;
res = PyString_FromStringAndSize(NULL, len);
if (!res)
return NULL;
start = out = PyString_AS_STRING(res);
while (pos<len)
{
if (in[pos] != '%')
*out++ = in[pos++];
else
{
char buffer[100];
if (pos+3>len || (in[pos+1] == 'u' && pos+6>len))
{
sprintf(buffer, "truncated escape at position %d", pos);
if (PyErr_Warn(PyExc_UserWarning, buffer))
{
Py_DECREF(res);
return NULL;
}
/* copy the characters literally */
while (pos<len)
*out++ = in[pos++];
}
else if (in[pos+1] == 'u')
{
if ((!isxdigit(in[pos+2])) || (!isxdigit(in[pos+3])) ||
(!isxdigit(in[pos+4])) || (!isxdigit(in[pos+5])))
{
int k;
sprintf(buffer, "malformed escape at position %d", pos);
if (PyErr_Warn(PyExc_UserWarning, buffer) < 0)
{
Py_DECREF(res);
return NULL;
}
for (k = 0; k < 6; ++k)
*out++ = in[pos + k];
}
else
{
char buffer[6];
int k;
for (k = 0; k < 4; ++k)
buffer[k] = in[pos + k + 2];
buffer[4] = '\0';
widechar_to_utf8(strtol(buffer, NULL, 16), &out);
}
pos += 6;
}
else
{
char buffer[3];
if ((!isxdigit(in[pos+1])) || (!isxdigit(in[pos+2])))
{
sprintf(buffer, "malformed escape at position %d", pos);
if (PyErr_Warn(PyExc_UserWarning, buffer) < 0)
{
Py_DECREF(res);
return NULL;
}
*out++ = in[pos];
*out++ = in[pos+1];
*out++ = in[pos+2];
}
else
{
buffer[0] = in[pos+1];
buffer[1] = in[pos+2];
buffer[2] = '\0';
*out++ = (char)strtol(buffer, NULL, 16);
}
pos += 3;
}
}
}
uni = PyUnicode_Decode(start, out-start, "utf-8", NULL);
if (uni || (!PyErr_ExceptionMatches(PyExc_UnicodeDecodeError)))
{
Py_DECREF(res);
return uni;
}
PyErr_Clear();
if (PyErr_Warn(PyExc_UserWarning, "malformed utf-8") < 0)
{
Py_DECREF(res);
return NULL;
}
uni = PyUnicode_Decode(start, out-start, "latin-1", NULL);
Py_DECREF(res);
return uni;
}
int appendempty(PyObject *newpath, int *pos)
{
PyObject *newsegment = Py_BuildValue("(u#)", pos, 0); /* pos is ignored */
if (!newsegment)
{
Py_DECREF(newpath);
return 0;
}
PyTuple_SET_ITEM(newpath, (*pos)++, newsegment);
return -1;
}
static char normalizepath__doc__[] =
"normalizepath(list) -> list\n\
\n\
Internal helper function for normalizing a path list";
/* the following function should be equivalent to RFC2396, Section 5.2 (6) (c)-(f)
* with the exception of removing empty path_segments. The equivalent Python
* code is:
new_path_segments = []
l = len(path_segments)
for i in xrange(l):
segment = path_segments[i]
if segment==(".",) or segment==("",):
if i==l-1:
new_path_segments.append(("",))
elif segment==("..",) and len(new_path_segments) and new_path_segments[-1]!=("..",):
new_path_segments.pop()
if i==l-1:
new_path_segments.append(("",))
else:
new_path_segments.append(segment)
return new_path_segments
*/
static PyObject *normalizepath(PyObject *self, PyObject *path)
{
PyObject *newpath;
PyObject *newpathlist;
int in;
int out;
int pathsize;
if (!PyList_Check(path))
{
PyErr_SetString(PyExc_TypeError, "normalizepath argument must be list");
return NULL;
}
pathsize = PyList_GET_SIZE(path);
newpath = PyTuple_New(pathsize);
if (!path)
return NULL;
out = 0;
for (in = 0; in < pathsize; ++in)
{
PyObject *segment = PyList_GET_ITEM(path, in);
PyObject *dir;
int segmentsize;
if (!PyTuple_CheckExact(segment) || (((segmentsize = PyTuple_GET_SIZE(segment)) != 1) && (segmentsize != 2)))
{
PyErr_SetString(PyExc_TypeError, "path entries must be tuples with 1 or 2 entries");
Py_DECREF(newpath);
return NULL;
}
dir = PyTuple_GET_ITEM(segment, 0);
if (!PyUnicode_CheckExact(dir))
{
PyErr_SetString(PyExc_TypeError, "path entry directory must be unicode");
Py_DECREF(newpath);
return NULL;
}
if (segmentsize == 1) /* we can only optimize it, if it doesn't have params */
{
int dirlen = PyUnicode_GET_SIZE(dir);
if ((dirlen==0) || ((dirlen==1) && (PyUnicode_AS_UNICODE(dir)[0] == '.'))) /* skip '' and '.' */
{
if (in==pathsize-1) /* add empty terminating segment */
if (!appendempty(newpath, &out))
return NULL;
continue; /* skip output */
}
else if ((dirlen == 2) && (PyUnicode_AS_UNICODE(dir)[0] == '.') && (PyUnicode_AS_UNICODE(dir)[1] == '.') && out) /* drop '..' and a previous real directory name */
{
PyObject *lastnewsegment = PyTuple_GET_ITEM(newpath, out-1);
int lastnewsegmentsize = PyTuple_GET_SIZE(lastnewsegment);
PyObject *lastnewsegmentdir = PyTuple_GET_ITEM(lastnewsegment, 0);
if (!((lastnewsegmentsize==1) && /* check that previous name is not '..' */
(PyUnicode_GET_SIZE(lastnewsegmentdir) == 2) &&
(PyUnicode_AS_UNICODE(lastnewsegmentdir)[0] == '.') &&
(PyUnicode_AS_UNICODE(lastnewsegmentdir)[1] == '.')))
{
Py_DECREF(lastnewsegment);
PyTuple_SET_ITEM(newpath, --out, NULL); /* drop previous */
if (in==pathsize-1) /* add empty terminating segment */
if (!appendempty(newpath, &out))
return NULL;
continue; /* skip output */
}
}
}
PyTuple_SET_ITEM(newpath, out++, segment); /* append segment to output */
Py_INCREF(segment);
}
/* Convert the result into a list */
newpathlist = PyList_New(out);
if (!newpathlist)
{
Py_DECREF(newpath);
return NULL;
}
for (in = 0; in<out; ++in)
{
PyObject *segment = PyTuple_GET_ITEM(newpath, in);
PyTuple_SET_ITEM(newpath, in, NULL); /* remove reference, because we copy the reference over to the result list, and drop the tuple afterwards */
PyList_SET_ITEM(newpathlist, in, segment);
}
Py_DECREF(newpath);
return newpathlist;
}
/* ==================================================================== */
/* python module interface */
static PyMethodDef _functions[] =
{
{"escape", escape, METH_VARARGS, escape__doc__},
{"unescape", unescape, METH_VARARGS, unescape__doc__},
{"normalizepath", normalizepath, METH_O, normalizepath__doc__},
{NULL, NULL}
};
void
#ifdef WIN32
__declspec(dllexport)
#endif
init_url(void)
{
Py_InitModule("_url", _functions);
}
syntax highlighted by Code2HTML, v. 0.9.1