1/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17//--------------------------------------------------------------------------------------------------
18//
19//   Module Name: dmSocketConnector.cpp
20//
21//   General Description: DmBrwConnector socket implementation class implementation file.  This allows
22//   DM to send and receive SYNCML data through HTTP protocol.  This implementation is platform independent.
23//   It can be used in any UNIX or LINUX machine.  It can allow use under Windows provided that Windows
24//   has the unix socket libraries.  This implementation also supports go through proxy server.
25//
26//   Proxy Usage:
27//
28//   setenv DM_PROXY_URL=123.567.28.167:1080            // please use IP address
29//   setenv DM_PROXY_AUTH="Basic ZTExNjdadsfagewwer"
30//
31//   Note: The sequences of letters after Basic is the B64 encoding of your
32//         userID:userPW.
33//
34//   Hint: Here is a web site to do B64 encoding:
35//         http://www.ericphelps.com/scripting/samples/Decode.htm
36
37#include <stdlib.h>        // Needed for exit()
38#include <string.h>        // Needed for strcpy() and strlen()
39#include <sys/types.h>     // Needed for system defined identifiers.
40#include <netinet/in.h>    // Needed for internet address structure.
41#include <sys/socket.h>    // Needed for socket(), bind(), etc...
42#include <arpa/inet.h>     // Needed for inet_ntoa()
43#include <unistd.h>        // Needed for close()
44#include <stdio.h>
45
46#include "dmSocketConnector.h"
47
48#define  BUF_SIZE   50000   // Buffer size
49
50static int  g_nPrintfEnabled = getenv("DM_NOPRINTF") == NULL;
51
52/*
53 *  Creates a socket connector.
54 *
55 *  @return a socket connector.
56 */
57DmSocketConnector * DmBrwCreateConnector() {
58    if ( g_nPrintfEnabled ) printf("\nCreate Socket Connector\n");
59    DmSocketConnector * socketHandler = NULL;
60    socketHandler = new DmSocketConnector();
61    return socketHandler;
62}
63
64/*
65 *  Destroy a socket connector.
66 *
67 *  @param the handler to a socket connector.
68 *  @return success if no error, otherwise return fail
69 */
70SYNCML_DM_RET_STATUS_T DmBrwDestroyConnector(DmSocketConnector * browser_handler) {
71    if ( g_nPrintfEnabled ) printf("Destroy Socket Connector\n");
72    if (browser_handler != NULL) {
73        delete browser_handler;
74    }
75    return SYNCML_DM_SUCCESS;
76}
77
78/*
79 *  Prepair a socket connection by parsing the URL to get information such
80 *  as host, port, and path.
81 *
82 *  @param url the URL to be parse
83 *  @param ConRef the connection reference
84 *  @param AddrType the type of a host address
85 *  @return success if get all connection information, otherwise return fail
86 */
87SYNCML_DM_RET_STATUS_T DmSocketConnector::Open(CPCHAR url, CPCHAR ConRef, int AddrType) {
88    if ( g_nPrintfEnabled ) printf("Open URL: %s\n", url);
89    DMString strURI = url;
90    DMString strAddrPort;
91    DMString strURIPath;
92
93    if (parseURL(strURI, strAddrPort, strURIPath)) {
94        if (parseAddrPort(strAddrPort, ipAddress, portNum)) {
95            proxy_url = getenv("DM_PROXY_URL");
96            if (proxy_url != NULL) {
97                urlPath = url;
98                DMString tmpURL = proxy_url;
99                if (!parseAddrPort(tmpURL, socket_ipAddress, socket_portNum)) {
100                    return SYNCML_DM_FAIL;
101                }
102            } else {
103                urlPath += "/";
104                urlPath += strURIPath;
105                socket_ipAddress = ipAddress;
106                socket_portNum = portNum;
107            }
108            return SYNCML_DM_SUCCESS;
109        }
110    }
111    return SYNCML_DM_FAIL;
112}
113
114/*
115 *  Dynamically change a socket connection by parsing a new URL to get information such
116 *  as host, port, and path.
117 *
118 *  @param url the URL to be parse
119 *  @param ConRef the connection reference
120 *  @param AddrType the type of a host address
121 *  @return success if get all connection information, otherwise return fail
122 */
123SYNCML_DM_RET_STATUS_T DmSocketConnector::SetUrl(CPCHAR url, CPCHAR ConRef, int AddrType) {
124    if ( g_nPrintfEnabled ) printf("Set URL: %s\n", url);
125    DMString strURI = url;
126    DMString strAddrPort;
127    DMString strURIPath;
128
129    if (parseURL(strURI, strAddrPort, strURIPath)) {
130        if (proxy_url != NULL) {
131            urlPath = url;
132        } else {
133            urlPath = "/";
134            urlPath += strURIPath;
135        }
136    }
137    return SYNCML_DM_SUCCESS;
138}
139
140/*
141 *  Sets the HTTP request method such as GET and POST.
142 *
143 *  @param method the method to be set
144 *  @return success if the method is supported, else return fail
145 */
146SYNCML_DM_RET_STATUS_T DmSocketConnector::SetRequestMethod(XPL_HTTP_METHOD_T method) {
147    requestMethod = method;
148    if ( method == XPL_HTTP_METHOD_POST ) {
149        if ( g_nPrintfEnabled ) printf("Request Method: POST   CODE: %d\n\n", method);
150        sentBuf = "POST ";
151        sentBuf += urlPath;
152        sentBuf += " HTTP/1.0\r\n";
153        sentBuf += "Host: ";
154        sentBuf += socket_ipAddress;
155        sentBuf += "\r\n";
156    } else if ( method == XPL_HTTP_METHOD_GET ) {
157        if ( g_nPrintfEnabled ) printf("Request Method: GET    CODE: %d\n", method);
158        sentBuf = "GET ";
159        sentBuf += urlPath;
160        sentBuf += " HTTP/1.1\r\n";
161    } else {
162      if ( g_nPrintfEnabled ) printf("Error: Request method not supported\n");
163      return SYNCML_DM_FAIL;
164    }
165    return SYNCML_DM_SUCCESS;
166}
167
168/*
169 *  Sets HTTP request property by appending to the sent buffer.
170 *  If DM_PROXY_AUTH enviornment variable was set, it will add
171 *  a proxy authorization properties in every new session.
172 *
173 *  @param header_start the name of a property
174 *  @param value_start_prt the value of the property
175 *  @return success after append to the sent buffer.
176 */
177SYNCML_DM_RET_STATUS_T DmSocketConnector::SetRequestProperty(CPCHAR props) {
178    if ( g_nPrintfEnabled ) printf("Property header: %s\n", props);
179    sentBuf += props;
180
181    if (proxy_auth == NULL && proxy_enable_check) {
182        proxy_auth = getenv("DM_PROXY_AUTH");       // proxy_auth=Basic userid:passwd
183        proxy_enable_check = false;
184    }
185
186    if (proxy_auth != NULL && new_session) {
187        if ( g_nPrintfEnabled ) printf ("Property header: Proxy-Authorization=%s\n", proxy_auth);
188        sentBuf += "Proxy-Authorization";
189        sentBuf += ": ";
190        sentBuf += proxy_auth;
191        sentBuf += "\r\n";
192        new_session = false;
193    }
194    return SYNCML_DM_SUCCESS;
195}
196
197/*
198 *  Send the data through socket and get the response back.
199 *
200 *  @data the data to be sent
201 *  @size the size of the data to be sent
202 *  @return success if no error when sending and getting response, else return fail
203 */
204SYNCML_DM_RET_STATUS_T DmSocketConnector::Send(CPCHAR data, UINT32 size) {
205    if (doSend(data, size) == SYNCML_DM_SUCCESS) {
206        return getResponse();
207    }
208    return SYNCML_DM_FAIL;
209}
210
211/*
212 *  Return the length of the response data.
213 *
214 *  @return the response data length
215 */
216UINT32 DmSocketConnector::GetResponseLength() {
217    if ( g_nPrintfEnabled ) printf("Get response data length: %d\n", responseLength);
218    return responseLength;
219}
220
221/*
222 *  Get the response data.
223 *
224 *  @param data the data to be fill with response data
225 *  @param size the size of the data
226 *  @return success if the data is filled, else return fail
227 */
228SYNCML_DM_RET_STATUS_T DmSocketConnector::GetResponse(char * data, UINT32 size) {
229    char tmpBuf[50000];
230    memcpy(data, responseData, responseLength);
231    if ( requestMethod == XPL_HTTP_METHOD_POST && size < 50000 ) {
232        memcpy(tmpBuf, responseData, responseLength);
233        tmpBuf[responseLength]=0;
234        if ( g_nPrintfEnabled ) printf("\nResponse Body: %s\n", tmpBuf);
235    }
236    return SYNCML_DM_SUCCESS;
237}
238
239/*
240 *  Get the value of a HTTP header feild based on header field name.
241 *
242 *  @param field the header field name
243 *  @param value the value of the header field
244 *  @return success if found the header field, else fail
245 *
246 */
247SYNCML_DM_RET_STATUS_T DmSocketConnector::GetHeaderField(CPCHAR field, char ** value) {
248    bool found = false;
249
250    for (int i = responseHeaders.begin(); i < responseHeaders.end(); i++) {
251        if (responseHeaders.get_key(i) == field) {
252            found = true;
253            break;
254        }
255    }
256
257    if (found == true) {
258       const DMString& s = responseHeaders.get(field).c_str();
259       *value = (char*)DmtMemAlloc( s.length() + 1 );
260       strcpy( *value, s );
261
262       if ( g_nPrintfEnabled ) printf("\nGet HeaderField %s : %s\n", field, *value);
263    } else {
264        if ( g_nPrintfEnabled ) printf("INFO: Can not find %s\n", field);
265        return SYNCML_DM_FAIL;
266    }
267    return SYNCML_DM_SUCCESS;
268}
269
270/*
271 *  Return HTTP response code such as 200, 404, and etc.
272 *
273 *  @return the HTTP response code
274 */
275XPL_HTTP_CODE_T DmSocketConnector::GetResponseCode() {
276    if ( g_nPrintfEnabled ) printf("\nGet Response Code: %s \n\n", responseCode.c_str());
277    return atoi(responseCode.c_str());
278}
279
280/*
281 *  Close the socket connection.
282 *
283 *  @return success if socket closed, else fail.
284 */
285SYNCML_DM_RET_STATUS_T DmSocketConnector::Close() {
286    if ( g_nPrintfEnabled ) printf("Close Socket Connector\n");
287    int ret = close(server_s);
288    if (ret != 0) {
289        if ( g_nPrintfEnabled ) printf("ERROR: Can not close the socket.\n");
290	return SYNCML_DM_FAIL;
291    }
292    return SYNCML_DM_SUCCESS;
293}
294
295/*
296 *  Close current session, but leave the connection open.
297 *
298 *  @return success after closed the session
299 */
300SYNCML_DM_RET_STATUS_T DmSocketConnector::CloseReq() {
301    if ( g_nPrintfEnabled ) printf("Close Socket Session\n");
302    new_session = true;
303    return SYNCML_DM_SUCCESS;
304}
305
306/*
307 *  Giving string and delimiter, the function will store the section of the string until the delimiter
308 *  in a string item.  This is similar to string tokenizer.
309 *
310 *  Ex:  abc:123:xyz      abc,123,and xyz are tokens seperated by a delimiter ':'
311 *
312 *  @param strItem the first section of the string until the delimiter
313 *  @param strReminder the rest of the string without the delimiter
314 *  @param cDelim the delimiter
315 *
316 */
317bool DmSocketConnector::DmStringParserGetItem( DMString& strItem, DMString& strReminder, char cDelim ) {
318    if ( strReminder[0] == 0 ) {
319        return false;
320    }
321    const char* s = strchr( strReminder, cDelim );
322    int nPos = s ? s - strReminder : -1;
323    if ( nPos < 0 ) {
324        strItem = strReminder;
325        strReminder = "";
326    } else {
327        strItem.assign( strReminder, nPos );
328        strReminder = DMString(s+1);
329    }
330    return true;
331}
332
333/*
334 *  Parse a HTTP response header.
335 *
336 *  @param strBuffer  the received data buffer from server that may contain the entire HTTP header
337 *  @param dataBufSize  number of bytes contained in strBuffer
338 *  @param strBufRemaining  the updated buffer containging HTTP body (i.e. strBuffer - HTTP header)
339 *  @param lenRemaining number of bytes in strBufRemaining
340 *  @return true if HTTP header marker "\r\n\r\n" is found in strBuffer, false otherwise
341 *
342 */
343bool DmSocketConnector::DmParseHTTPHeader( char* strBuffer, int dataBufSize, char** strBufRemaining, int& lenRemaining) {
344    // Let's get the response code first
345    // If we do not see end of HTTP header, do not bother to parse
346    char* entireHeaderEnd = strstr( strBuffer, "\r\n\r\n" );
347    if ( !entireHeaderEnd )
348        return false;
349
350    // Let's get the response code by looking for first space in response
351    char* pFirstSpace = strstr( strBuffer, " ");
352    pFirstSpace++;
353    char tmpBuf[10];
354    strncpy(tmpBuf, pFirstSpace, 3);
355    tmpBuf[3]=0;
356    responseCode = tmpBuf;
357    char* curPos = strBuffer;
358    // skip one \r\n
359    char *headerEnd = strstr(curPos, "\r\n");
360    curPos = headerEnd + strlen("\r\n");
361    headerEnd = strstr(curPos, "\r\n");
362
363    // Found an HTTP Header, let's get the name and value pair
364    while ( headerEnd != NULL ) {
365        char* pColon = strchr(curPos, ':');
366        char name[256];
367        char value[256];
368        strncpy(name, curPos, pColon-curPos);
369        name[pColon-curPos]=0;
370        strncpy(value, pColon+2, headerEnd-pColon-2);
371        value[headerEnd-pColon-2]=0;
372        responseHeaders.put(name, value);
373        if ( headerEnd == entireHeaderEnd )
374            break;
375        curPos = headerEnd + strlen("\r\n");
376        headerEnd = strstr(curPos, "\r\n");
377    }
378    *strBufRemaining = entireHeaderEnd + strlen("\r\n\r\n");
379    lenRemaining = dataBufSize - (*strBufRemaining - strBuffer);
380    return true;
381}
382
383/*
384 *  Parse a HTTP response header.
385 *
386 *  @param newData  pointer to the buffer containing data from server
387 *  @param len  the size of the data in the buffer
388 *  @return true if data is set successfully, false if memory can not be allocated
389 */
390bool DmSocketConnector::SetResponseData(unsigned char* newData, int len) {
391    if (len == 0) {
392        return true;
393    }
394
395    if ( responseData == NULL ) {
396        responseData = (unsigned char*)malloc(len);
397        memcpy( responseData, newData, len);
398    } else {
399        unsigned char* newPtr = (unsigned char*)malloc(len + responseLength);
400        memcpy((void*)newPtr, (void*)responseData, responseLength);
401        memcpy((void*)(newPtr+responseLength), (void*)newData, len);
402        free(responseData);
403        responseData = newPtr;
404    }
405    return true;
406}
407
408/*
409 *  Parse the URL into address:port and URL path.
410 *
411 *  @param strURI the URI to be parse
412 *  @param strAddrPort the string to store the address:port
413 *  @param strURIPath the string to store the path of the URI
414 *
415 *  @return true if URI was in right format, else false
416 */
417bool DmSocketConnector::parseURL(DMString strURI, DMString& strAddrPort, DMString& strURIPath) {
418    int counter = 0;
419    DMString tmpStr;
420
421    while(DmStringParserGetItem(tmpStr, strURI, '/')) {
422        if (counter == 0) {
423            if (strcmp(tmpStr.c_str(), "http:") != 0) {
424                return false;
425            }
426        } else if (counter == 1) {
427            if (tmpStr.c_str()[0]!=0/*strcmp(tmpStr.c_str(), "") !=0*/) {
428                return false;
429            }
430        } else if (counter == 2) {
431            strAddrPort = tmpStr;
432            strURIPath = strURI;
433            return true;
434        }
435        counter++;
436    }
437    return false;
438}
439
440/*
441 *  Parse the address:port into address and port.
442 *
443 *  @param strAddrPort the string that holds address:port
444 *  @param strAddr the string that store address
445 *  @param strPort the string that store port
446 *
447 *  @return true after parsing
448 */
449bool DmSocketConnector::parseAddrPort(DMString strAddrPort, DMString& strAddr, DMString& strPort) {
450    int j = 0;
451    DMString tmpStr;
452    while(DmStringParserGetItem(tmpStr, strAddrPort, ':')) {
453        if (j == 0) {
454            strAddr = tmpStr;
455        } else if (j == 1) {
456            strPort = tmpStr;
457        }
458        j++;
459    }
460    return true;
461}
462
463/*
464 *  Prepair HTTP sent with sent data and sent it out through socket.
465 *
466 *  @param data the body of the request
467 *  @param size the size of the request body
468 *
469 *  @return success if it sent all data through socket, else fail
470 */
471SYNCML_DM_RET_STATUS_T DmSocketConnector::doSend(CPCHAR data, UINT32 size) {
472    unsigned int retcode;        // Return code
473
474    if ( g_nPrintfEnabled ) printf("\n[Header: %d bytes]\n%s\n", strlen(sentBuf.c_str()), sentBuf.c_str());
475    if ( g_nPrintfEnabled ) printf("[Data: %d bytes]\n%s\n\n", strlen(data), data);
476
477    if (size != 0) {
478        sentBuf += "Content-length: ";
479
480        char size_str[10];
481        sprintf(size_str, "%d", size);
482
483        sentBuf += size_str;
484        sentBuf += "\r\n\r\n";
485    } else {
486        sentBuf += "Host: ";
487        sentBuf += socket_ipAddress;
488        sentBuf += "\r\n\r\n";
489    }
490
491    server_s = socket(AF_INET, SOCK_STREAM, 0);
492    server_addr.sin_family = AF_INET;                // Address family to use
493
494    // Port num to use
495    server_addr.sin_port = htons(atoi(socket_portNum.c_str()));
496    // IP address to use
497    server_addr.sin_addr.s_addr = inet_addr(socket_ipAddress);
498
499    if ( g_nPrintfEnabled ) printf("Host: %s  Port: %s\n", socket_ipAddress.c_str(), socket_portNum.c_str());
500
501    // Do a connect (connect() blocks)
502    retcode = connect(server_s, (struct sockaddr *)&server_addr,
503                      sizeof(server_addr));
504
505    if (retcode != 0) {
506        if ( g_nPrintfEnabled ) printf("ERROR: connect() failed \n");
507        return SYNCML_DM_FAIL;
508    }
509
510    //memset(out_buf, 0, BUF_SIZE);
511    //strcpy(out_buf, sentBuf.c_str());
512
513    if ( g_nPrintfEnabled ) printf("Send Size: %d\n", size);
514    if ( g_nPrintfEnabled ) printf("\nSend\n>>> >>> >>>\n%s%s<<< <<< <<<\n", sentBuf.c_str(), data);
515
516    // Send a request to the server
517    int ret = send(server_s, sentBuf.c_str(), strlen(sentBuf.c_str()), 0);
518    ret = send(server_s, data, size, 0);
519
520    if ( g_nPrintfEnabled ) printf("Send Size: %d\n", ret);
521
522    if (ret != -1) {
523      return SYNCML_DM_SUCCESS;
524    }
525    return SYNCML_DM_FAIL;
526}
527
528/*
529 *  Get HTTP response by parsing the header information and body.
530 *
531 *  @return success if the response size is greater than zero, else fail
532 */
533SYNCML_DM_RET_STATUS_T DmSocketConnector::getResponse() {
534    if ( g_nPrintfEnabled ) printf("\nGet Response\n");
535    char in_buf[BUF_SIZE];   // Input buffer for response
536    bool handleHeader = true;
537    int retcode;
538    int nBufUsed = 0;
539    responseBody = "";
540    responseLength = 0;
541
542    if ( responseData != NULL ) {
543        free(responseData);
544        responseData = NULL;
545    }
546
547    retcode = recv(server_s, in_buf, BUF_SIZE, 0);
548    if ( g_nPrintfEnabled ) printf("Size: %d\n", retcode);
549
550    while ((retcode > 0) && (retcode != -1)) {
551        int lenRemaining;
552        bool bEndHeader;
553        char* strRemaining;
554
555        if ( handleHeader )  {
556            bEndHeader = DmParseHTTPHeader( in_buf, retcode, &strRemaining, lenRemaining);
557            if ( bEndHeader ) {
558                handleHeader = false;
559                SetResponseData((unsigned char*)strRemaining,lenRemaining);
560                responseLength = lenRemaining;
561                nBufUsed = 0;
562            }
563            else
564              nBufUsed += retcode;
565        } else {
566            SetResponseData((unsigned char*)in_buf,retcode);
567            responseLength += retcode;
568        }
569        retcode = recv(server_s, in_buf + nBufUsed, BUF_SIZE-nBufUsed, 0);
570    }
571
572    if ( responseLength > 0 ) {
573        return SYNCML_DM_SUCCESS;
574    } else {
575        return SYNCML_DM_FAIL;
576    }
577}
578