使用C语言解析URL的最佳方法是什么?

38

我有一个像这样的URL:

http://192.168.0.1:8080/servlet/rece

我想解析URL以获取值:

IP: 192.168.0.1
Port: 8080
page:  /servlet/rece

我该怎么做?


对于 Windows 系统,使用 CoInternetParseUrl。 - Jichao
10个回答

29

个人而言,我会窃取HTParse.c模块来自W3C(例如,在lynx网络浏览器中使用)。然后,您可以进行以下操作:

 strncpy(hostname, HTParse(url, "", PARSE_HOST), size)

使用经过充分验证和调试的库的重要性在于,您不会陷入典型的URL解析陷阱中(例如,当主机是IP地址时,许多正则表达式会失败,特别是IPv6地址)。

1
特别是要注意,在IPv6中,如果您尝试使用冒号分隔符,则存在歧义情况。例如3ffe:0501::1:2,这是端口2还是带有默认端口的完整地址。URL规范已经处理了这个问题,预编写的库也是如此。 - bitmusher
3
请注意,这里没有真正的歧义。URI标准RFC 3986是明确的,你的示例是非法的(需要使用方括号)。 - bortzmeyer
3
谢谢,这让我感到安慰。我误以为用户界面代码(比如浏览器地址栏)会接受没有方括号的地址。对几个流行浏览器的快速检查表明情况并非如此。 - bitmusher
1
HTParse.c 有许多依赖项,你能否解释一下如何轻松地从项目中“窃取”它?也许在2009年这是不可能的 ;) - Carson Reinke

15

我使用sscanf编写了一个简单的代码,可以解析非常基本的URL。

#include <stdio.h>

int main(void)
{
    const char text[] = "http://192.168.0.2:8888/servlet/rece";
    char ip[100];
    int port = 80;
    char page[100];
    sscanf(text, "http://%99[^:]:%99d/%99[^\n]", ip, &port, page);
    printf("ip = \"%s\"\n", ip);
    printf("port = \"%d\"\n", port);
    printf("page = \"%s\"\n", page);
    return 0;
}

./urlparse
ip = "192.168.0.2"
port = "8888"
page = "servlet/rece"

这是哪个平台?我不知道你可以在sscanf格式中放置像[^:]这样的正则表达式。 - Jeroen Dirks
我的平台是:uname -a Linux ubuntu 2.6.24-21-generic #1 SMP Tue Oct 21 23:43:45 UTC 2008 i686 GNU/Linux - Jiang Bian
7
在这种情况下,[^:]不是正则表达式,它仅仅是sscanf()的一个特殊格式说明符。它是标准的,比如可以查看这个手册页面:http://linux.die.net/man/3/sscanf。 - unwind
3
如果没有端口号,分析存在一些错误,它不能正常工作。我该如何修复它? - Jiang Bian
这里的%99是什么作用?它是如何工作的?请指导。 - Supriya Bhide

11

也许会晚一些,但我想分享一下我的解决方案——我使用了 http_parser_parse_url() 函数和从 Joyent/HTTP parser 库中分离出来的必需宏 - 这很有效率,只用了 ~600 行代码。


没错。Node.js的HTTP解析器库非常出色,对于任何涉及HTTP请求/响应的事情都经过了很好的测试。 - Jan Jongboom

11

1
确实,使用库似乎是唯一合理的选择,因为有许多陷阱(http vs. https、显式端口、路径中的编码等)。 - bortzmeyer
嗨,我为URL编写了一个BNF,像这样。 URL =“http://”{IP}{PORT}?{PAGE}? Flex生成了一个解析URL的文件。但是如何从URL中获取单独的部分,例如IP、PORT和PAGE。 - Pratapi Hemant Patel

3

Libcurl现在有一个curl_url_get()函数可以提取主机、路径等信息。

示例代码:https://curl.haxx.se/libcurl/c/parseurl.html

/* extract host name from the parsed URL */ 
uc = curl_url_get(h, CURLUPART_HOST, &host, 0);
if(!uc) {
  printf("Host name: %s\n", host);
  curl_free(host);
}

2
这个链接http://draft.scyphus.co.jp/lang/c/url_parser.html提供了两个文件(*.c, *.h),它们的体积更小,对我非常有效。我需要根据[1]进行代码调整。

[1]将所有函数调用中的http_parsed_url_free(purl)改为parsed_url_free(purl)

   //Rename the function called
   //http_parsed_url_free(purl);
   parsed_url_free(purl);

2
@tremendows:非常好的链接。它运行得像魔法一样。 - nitin_cherian
4
遗憾的是,这个优秀的代码受版权保护,“保留所有权利”,因此除了个人项目外,不应该被使用。 - Jim In Texas

2

基于sscanf()的纯解决方案:

//Code
#include <stdio.h>

int
main (int argc, char *argv[])
{
    char *uri = "http://192.168.0.1:8080/servlet/rece"; 
    char ip_addr[12], path[100];
    int port;
    
    int uri_scan_status = sscanf(uri, "%*[^:]%*[:/]%[^:]:%d%s", ip_addr, &port, path);
    
    printf("[info] URI scan status : %d\n", uri_scan_status);
    if( uri_scan_status == 3 )
    {   
        printf("[info] IP Address : '%s'\n", ip_addr);
        printf("[info] Port: '%d'\n", port);
        printf("[info] Path : '%s'\n", path);
    }
    
    return 0;
}


然而,请记住这个解决方案是专门为[protocol_name]://[ip_address]:[port][/path]类型的URI定制的。如果想了解URI语法中存在的组件,可以前往RFC 3986
现在让我们来分解我们的特制格式字符串:"%*[^:]%*[:/]%[^:]:%d%s"
  • %*[^:]帮助忽略协议/方案(例如http、https、ftp等)

    它基本上捕获从开头到第一次遇到:字符的字符串。由于我们在%字符后面使用了*,因此捕获的字符串将被忽略。

  • %*[:/]帮助忽略协议和IP地址之间的分隔符,即://

  • %[^:]帮助捕获分隔符后面的字符串,直到遇到:。这个捕获的字符串就是IP地址。

  • :%d帮助捕获:字符右侧的数字(在捕获IP地址期间遇到的数字)。在这里捕获的数字基本上是您的端口号。

  • %s如您所知,将帮助您捕获剩余的字符串,这些字符串仅是您正在查找的资源的路径。


1

我写了这个

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
typedef struct
{
    const char* protocol = 0;
    const char* site = 0;
    const char* port = 0;
    const char* path = 0;
} URL_INFO;
URL_INFO* split_url(URL_INFO* info, const char* url)
{
    if (!info || !url)
        return NULL;
    info->protocol = strtok(strcpy((char*)malloc(strlen(url)+1), url), "://");
    info->site = strstr(url, "://");
    if (info->site)
    {
        info->site += 3;
        char* site_port_path = strcpy((char*)calloc(1, strlen(info->site) + 1), info->site);
        info->site = strtok(site_port_path, ":");
        info->site = strtok(site_port_path, "/");
    }
    else
    {
        char* site_port_path = strcpy((char*)calloc(1, strlen(url) + 1), url);
        info->site = strtok(site_port_path, ":");
        info->site = strtok(site_port_path, "/");
    }
    char* URL = strcpy((char*)malloc(strlen(url) + 1), url);
    info->port = strstr(URL + 6, ":");
    char* port_path = 0;
    char* port_path_copy = 0;
    if (info->port && isdigit(*(port_path = (char*)info->port + 1)))
    {
        port_path_copy = strcpy((char*)malloc(strlen(port_path) + 1), port_path);
        char * r = strtok(port_path, "/");
        if (r)
            info->port = r;
        else
            info->port = port_path;
    }
    else
        info->port = "80";
    if (port_path_copy)
        info->path = port_path_copy + strlen(info->port ? info->port : "");
    else 
    {
        char* path = strstr(URL + 8, "/");
        info->path = path ? path : "/";
    }
    int r = strcmp(info->protocol, info->site) == 0;
    if (r && info->port == "80")
        info->protocol = "http";
    else if (r)
        info->protocol = "tcp";
    return info;
}

测试

int main()
{
    URL_INFO info;
    split_url(&info, "ftp://192.168.0.1:8080/servlet/rece");
    printf("Protocol: %s\nSite: %s\nPort: %s\nPath: %s\n", info.protocol, info.site, info.port, info.path);
    return 0;
}

出口

Protocol: ftp
Site: 192.168.0.1
Port: 8080
Path: /servlet/rece

1
这个C语言代码片段可能会很有用。它使用sscanf实现了一个纯C解决方案。

https://github.com/luismartingil/per.scripts/tree/master/c_parse_http_url

它使用

// Parsing the tmp_source char*
if (sscanf(tmp_source, "http://%99[^:]:%i/%199[^\n]", ip, &port, page) == 3) { succ_parsing = 1;}
else if (sscanf(tmp_source, "http://%99[^/]/%199[^\n]", ip, page) == 2) { succ_parsing = 1;}
else if (sscanf(tmp_source, "http://%99[^:]:%i[^\n]", ip, &port) == 2) { succ_parsing = 1;}
else if (sscanf(tmp_source, "http://%99[^\n]", ip) == 1) { succ_parsing = 1;}
(...)

第三个if语句将永远不会被测试,因为第二个if语句具有相同的含义,这可能会对端口/页面造成问题。 - Risinek

-3

编写自定义解析器或使用其中一个字符串替换函数来替换分隔符 ':',然后使用 sscanf()


22
有许多陷阱需要注意,因此对我而言,自定义解析器似乎是个不好的想法。 - bortzmeyer
1
@bortzmeye:这并不意味着建议无效。这是模糊的推理。此外,自定义解析器是最强大/高效/无依赖性的。sscanf更容易出错。 - dirkgently
30
“写一些能够满足你需求的代码”是一个被接受的答案吗? - Spike0xff

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接