1 /* An XMLRPC client and server library for GNet
2  *
3  * Copyright (c) 2006 Dov Grobgeld <dov.grobgeld@gmail.com>
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Lesser General Public
7  * License as published by the Free Software Foundation; either
8  * version 2 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public
16  * License along with this library; if not, write to the
17  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18  * Boston, MA 02111-1307, USA.
19  *
20  */
21 
22 #include <stdio.h>
23 #include <string.h>
24 #include <stdlib.h>
25 #include "xmlrpc.h"
26 
27 typedef struct  {
28   GNetXmlRpcClient client;
29   GTcpSocket* socket;
30   GString *xmlrpc_string;
31   gboolean in_header;
32   gint header_lineno;
33   gint message_size;
34   gint error;
35 } GNetXmlRpcClientPrivate;
36 
37 static gchar *build_xmlrpc_message(const gchar *method,
38                                    const gchar *param);
39 
gnet_xmlrpc_client_new(const gchar * hostname,int port)40 GNetXmlRpcClient *gnet_xmlrpc_client_new(const gchar *hostname,
41                                          int port)
42 {
43   GNetXmlRpcClientPrivate *client = g_new0(GNetXmlRpcClientPrivate, 1);
44   GInetAddr* addr;
45 
46   addr = gnet_inetaddr_new (hostname, port);
47   if (!addr)
48     {
49       fprintf (stderr, "Error: Name lookup for %s failed\n", hostname);
50       return NULL;
51     }
52 
53   /* Create the socket */
54   client->socket = gnet_tcp_socket_new (addr);
55   gnet_inetaddr_delete (addr);
56   if (!client->socket)
57     {
58       fprintf (stderr, "Error: Could not connect to %s:%d\n", hostname, port);
59       return NULL;
60     }
61 
62   client->client.gnet_channel = gnet_tcp_socket_get_io_channel (client->socket);
63   g_assert (client->client.gnet_channel != NULL);
64 
65   client->xmlrpc_string = g_string_new("");
66   client->in_header = TRUE;
67   client->header_lineno = 0;
68   client->message_size = 0;
69   client->error = 0;
70 
71   return (GNetXmlRpcClient*)client;
72 }
73 
gnet_xmlrpc_client_delete(GNetXmlRpcClient * _client)74 void gnet_xmlrpc_client_delete(GNetXmlRpcClient *_client)
75 {
76   GNetXmlRpcClientPrivate *client = (GNetXmlRpcClientPrivate*)_client;
77 
78   g_string_free(client->xmlrpc_string, TRUE);
79   gnet_tcp_socket_delete (client->socket);
80   g_free(client);
81 }
82 
gnet_xmlrpc_client_call(GNetXmlRpcClient * _client,const gchar * method,const gchar * param,gchar ** reply)83 int gnet_xmlrpc_client_call(GNetXmlRpcClient *_client,
84                             const gchar *method,
85                             const gchar *param,
86                             // output
87                             gchar **reply)
88 {
89   GNetXmlRpcClientPrivate *client = (GNetXmlRpcClientPrivate*)_client;
90   gchar *xmlrpc_message = build_xmlrpc_message(method, param);
91   gchar *msg_start, *msg_end;
92   gchar *p;
93   GString *xmlrpc_string = g_string_new("");
94   GString *reply_string = g_string_new("");
95   gsize n;
96   gchar buffer[1024];
97   gint error;
98 
99   n = strlen(xmlrpc_message);
100   //  printf("Writing...\n"); fflush(stdout);
101   error = gnet_io_channel_writen (client->client.gnet_channel,
102                                   xmlrpc_message, n, &n);
103   // printf("error = %d\n", error); fflush(stdout);
104   if (error != G_IO_ERROR_NONE)
105       return -1;
106 
107   // fprintf(stderr, "entering while loop\n");
108   while (gnet_io_channel_readline (client->client.gnet_channel, buffer, sizeof(buffer), &n) == G_IO_ERROR_NONE)
109     {
110       if (client->in_header)
111         {
112           // Check for a valid response
113           if (client->header_lineno == 0)
114             {
115               // If we don't have HTTP we've got a problem
116               if (g_strstr_len(buffer, 5, "HTTP") == NULL)
117                 {
118                   return -1;
119                 }
120             }
121           else if (n==1)
122             {
123               client->in_header = FALSE;
124             }
125           else
126             {
127               // Look for the content-length string case independant.
128               char *p;
129 
130               // Lower case buf
131               p = buffer;
132               while(*p)
133                 {
134                   *p = g_ascii_tolower(*p);
135                   p++;
136                 }
137 
138               // Search for string
139               if ((p = g_strstr_len(buffer, n-1,
140                                     "content-length:")) != NULL)
141                 {
142                   p += strlen("Content-length:");
143                   client->message_size = atoi(p);
144                 }
145             }
146           client->header_lineno++;
147         }
148       // If we are not in the header then append the line to the xmlrpc string.
149       else
150         {
151           g_string_append_len(xmlrpc_string, buffer, n-1);
152           g_string_append_c(xmlrpc_string, '\n');
153         }
154 
155       // Check if we are finished
156       if (xmlrpc_string->len
157           && xmlrpc_string->len >= client->message_size)
158         {
159           // Quit and reset parsing for next message
160           client->in_header = 1;
161           client->header_lineno = 0;
162           break;
163         }
164     }
165 
166   // Extract the response. Should be exchanged to some more robust
167   // XML parsing.
168   msg_start = g_strstr_len(xmlrpc_string->str,
169                            xmlrpc_string->len,
170                            "<value><string>");
171   if (msg_start == NULL)
172     return -1;
173 
174   msg_start += strlen("<value><string>");
175   msg_end = g_strrstr(msg_start, "</string></value>");
176 
177   // Decode the response
178   p = msg_start;
179   while(*p != '<')
180     {
181       gchar c = *p++;
182 
183       if (c == '&')
184         {
185           if (g_strstr_len(p, 4, "amp;") == p)
186             {
187               g_string_append_c(reply_string, '&');
188               p+= 4;
189             }
190           else if (g_strstr_len(p, 3, "lt;") == p)
191             {
192               g_string_append_c(reply_string, '<');
193               p+= 3;
194             }
195           else if (g_strstr_len(p, 3, "gt;") == p)
196             {
197               g_string_append_c(reply_string, '>');
198               p+= 3;
199             }
200           else
201             {
202               // Don't know what to do. Just add the ampersand..
203               g_string_append_c(reply_string, '&');
204             }
205         }
206       else
207         g_string_append_c(reply_string, c);
208     }
209   *reply = reply_string->str;
210   g_string_free(reply_string, FALSE);
211   g_string_free(xmlrpc_string, TRUE);
212 
213   return 0;
214 }
215 
build_xmlrpc_message(const gchar * method,const gchar * param)216 static gchar *build_xmlrpc_message(const gchar *method,
217                                    const gchar *param)
218 {
219   GString *xmlrpc_msg = g_string_new("");
220   GString *req_string = g_string_new("");
221   const gchar *p;
222   gchar *ret;
223 
224   g_string_append_printf(xmlrpc_msg,
225                          "<?xml version=\"1.0\"?>\n"
226                          "<methodCall>\n"
227                          "<methodName>%s</methodName>\n"
228                          "<params>\n"
229                          "<param><value><string>",
230                          method);
231 
232   // Encode the param string
233   p = param;
234   while(*p)
235     {
236       gchar c = *p++;
237       switch (c)
238         {
239         case '&' : g_string_append(xmlrpc_msg, "&amp;"); break;
240         case '<': g_string_append(xmlrpc_msg, "&lt;"); break;
241         case '>': g_string_append(xmlrpc_msg, "&gt;"); break;
242         default:
243           g_string_append_c(xmlrpc_msg, c);
244         }
245     }
246 
247   g_string_append(xmlrpc_msg,
248                   "</string></value></param>\n"
249                   "</params>\n"
250                   "</methodCall>\n");
251 
252   g_string_append_printf(req_string,
253                          "POST /RPC2 HTTP/1.0\n"
254                          "User-Agent: Frontier/5.1.2 (WinNT)\n"
255                          "Host: localhost\n"
256                          "Content-Type: text/xml\n"
257                          "Content-length: %u\n\n",
258                          (guint) xmlrpc_msg->len);
259   g_string_append_len(req_string,
260                       xmlrpc_msg->str,
261                       xmlrpc_msg->len);
262   g_string_free(xmlrpc_msg, TRUE);
263 
264   ret = req_string->str;
265   g_string_free(req_string, FALSE);
266 
267   return ret;
268 }
269