在可能的情况下尽早返回第一,很好地运用了先前审查的要点。您似乎不确定的一件事是提前返回--我将在这里对您的代码进行重构,以展示它是什么样子(我不会在实际的生产代码中包含额外的注释):
代码语言:javascript运行复制#include
#include
#include
int main(void)
{
char *stdInData;
char *requestMethod;
char *contentLenString;
int contentLength;
char *user;
char *pass;
char *stdInDataCpy;
requestMethod = getenv("REQUEST_METHOD");
if ( strcmp(requestMethod,"POST") != 0 ) {
printf("Status: 405 Method Not Allowed\n\n");
/* the program successfully detected the user error! */
return EXIT_SUCCESS;
}
/* If we got here, the request method is POST */
contentLenString = getenv("CONTENT_LENGTH");
contentLength = atoi(contentLenString);
if ( contentLength == 0 ) {
printf("Status: 400 Bad Request\n\n");
/* the program successfully detected the user error! */
return EXIT_SUCCESS;
}
/* If we got here, we have a content length */
stdInData = malloc(contentLength + 1);
if (!stdInData) {
printf("Status: 500 Server Error\n\n");
return EXIT_FAILURE;
}
/* If we got here, we have a buffer to read into */
fgets(stdInData, contentLength + 1, stdin);
stdInDataCpy = malloc(contentLength + 1);
if ( stdInDataCpy ){
strcpy(stdInDataCpy, stdInData);
pass = strtok(stdInDataCpy, "&");
pass = strtok(NULL, "&");
pass = strtok(pass, "=");
pass = strtok(NULL, "=");
}
user = strtok(stdInData, "&");
user = strtok(user, "=");
user = strtok(NULL, "=");
printf("Content-type: text/html\n\n");
printf("\n");
printf("
\n");
printf("
Takelogin\n");
printf("\n");
printf("
\n");
printf("%s@%s", user, pass);
free(stdInData);
free(stdInDataCpy);
printf("\n");
printf("\n");
return EXIT_SUCCESS;
}您看到代码是如何不再深深缩进的吗?而且我们在条件代码中不再有if,所以我们一次只需要跟踪一个条件?
添加了更多的防御检查第一次重构帮助我们识别一些库调用,这些调用的失败我们还没有很好地处理:
如果环境变量未设置,getenv()可以返回空指针。如果出现错误,fgets()将返回一个空指针。如果第二个malloc()失败,我们将忽略它,并且pass未初始化。strtok()可以返回一个空指针。printf()也可能失败,在这种情况下,我们没有正确地执行我们的函数(我们可以在关闭流时发现失败)。下一个版本添加了这些检查(变量声明移到可以初始化的位置):
代码语言:javascript运行复制int main(void)
{
const char *const requestMethod = getenv("REQUEST_METHOD");
if (!requestMethod || strcmp(requestMethod,"POST") != 0) {
printf("Status: 405 Method Not Allowed\n\n");
return EXIT_SUCCESS;
}
const char *const contentLenString = getenv("CONTENT_LENGTH");
int contentLength = contentLenString ? atoi(contentLenString) : 0;
if (contentLength == 0) {
printf("Status: 400 Bad Request\n\n");
return EXIT_SUCCESS;
}
char *const stdInData = malloc(contentLength + 1);
char *const stdInDataCpy = malloc(contentLength + 1);
if (!stdInData || !stdInDataCpy || !fgets(stdInData, contentLength + 1, stdin)) {
printf("Status: 500 Server Error\n\n");
return EXIT_FAILURE;
}
strcpy(stdInDataCpy, stdInData);
const char *pass = strtok(stdInDataCpy, "&");
pass = strtok(NULL, "&");
pass = strtok(NULL, "=");
pass = strtok(NULL, "=");
const char *user = strtok(stdInData, "&");
user = strtok(NULL, "=");
user = strtok(NULL, "=");
if (!pass || !user) {
printf("Status: 400 Missing data\n\n");
free(stdInData);
free(stdInDataCpy);
return EXIT_SUCCESS;
}
printf("Content-type: text/html\n\n"
"\n"
"
\n"
"
Takelogin\n"
"\n"
"
\n"
"%s@%s"
"\n"
"\n",
user, pass);
free(stdInData);
free(stdInDataCpy);
return fclose(stdout) ? EXIT_FAILURE : EXIT_SUCCESS;
}用户输入是敌对的外来的输入是有害的。例如,如果我们传递一个负的内容长度参数会发生什么?
即使不是积极敌对,善意的用户代理也可以按任何顺序传递表单变量user和pass,并且可能提供比我们预期的更多的变量,因此我们不能依赖所需参数的确切位置:我们需要更彻底地检查它们。
最后,变量可能包含我们不希望直接包含到文本中的文本(即跨站点脚本或XSS攻击)。
我们可以使用一些辅助函数来使代码更加健壮:
代码语言:javascript运行复制#include
#include
#include
/* return value (to right of = sign) if s begins with key=, else null */
static const char* val_if_param(const char *s, const char *key)
{
const size_t key_len = strlen(key);
if (!strncmp(s, key, key_len) && s[key_len] == '=') {
return s + key_len + 1;
}
return NULL;
}
static void html_print(const char *s)
{
for (; *s; ++s) {
switch (*s) {
case '&': printf("&"); break;
case '<': printf("<"); break;
default: putchar(*s); break;
}
}
}
int main(void)
{
const char *const requestMethod = getenv("REQUEST_METHOD");
if (!requestMethod || strcmp(requestMethod,"POST") != 0) {
printf("Status: 405 Method Not Allowed\n\n");
return EXIT_SUCCESS;
}
const char *const contentLenString = getenv("CONTENT_LENGTH");
int contentLength = contentLenString ? atoi(contentLenString) : 0;
if (contentLength <= 0) {
printf("Status: 400 Bad Request\n\n");
return EXIT_SUCCESS;
}
++contentLength; /* allow for null terminator */
char *const stdInData = malloc((size_t)contentLength);
if (!stdInData || !fgets(stdInData, contentLength, stdin)) {
printf("Status: 500 Server Error\n\n");
return EXIT_FAILURE;
}
const char *user = NULL;
const char *pass = NULL;
for (char *s = strtok(stdInData, "&"); s; s = strtok(NULL, "&")) {
if (!pass) { pass = val_if_param(s, "pass"); }
if (!user) { user = val_if_param(s, "user"); }
}
if (!pass || !user) {
printf("Status: 400 Missing data\n\n");
free(stdInData);
return EXIT_SUCCESS;
}
puts("Content-type: text/html\n\n"
"\n"
"
\n"
"
Takelogin\n"
"\n"
"
");
html_print(user);
putchar('@');
html_print(pass);
puts("\n\n"
"\n");
free(stdInData);
return fclose(stdout) ? EXIT_FAILURE : EXIT_SUCCESS;
}