1 /*
2 * Copyright (C) 2004-2008 Christos Tsantilas
3 *
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2.1 of the License, or (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
13 *
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
17 * MA 02110-1301 USA.
18 */
19
20 #include "common.h"
21 #include "c-icap.h"
22 #include <stdio.h>
23 #include <fcntl.h>
24 #include <ctype.h>
25 #include "debug.h"
26 #include "header.h"
27
28
29 const char *ci_common_headers[] = {
30 "Cache-Control",
31 "Connection",
32 "Date",
33 "Expires",
34 "Pragma",
35 "Trailer",
36 "Upgrade",
37 /*And ICAP speciffic headers ..... */
38 "Encapsulated"
39 };
40
41
42
43 const char *ci_methods[] = {
44 "", /*0x00 */
45 "OPTIONS", /*0x01 */
46 "REQMOD", /*0x02 */
47 "", /*0x03 */
48 "RESPMOD" /*0x04 */
49 };
50
51
52 const char *ci_request_headers[] = {
53 "Authorization",
54 "Allow",
55 "From",
56 "Host", /*REQUIRED ...... */
57 "Referer",
58 "User-Agent",
59 /*And ICAP specific headers ..... */
60 "Preview"
61 };
62
63 const char *ci_responce_headers[] = {
64 "Server",
65 /*ICAP spacific headers */
66 "ISTag"
67 };
68
69 const char *ci_options_headers[] = {
70 "Methods",
71 "Service",
72 "ISTag",
73 "Encapsulated",
74 "Opt-body-type",
75 "Max-Connections",
76 "Options-TTL",
77 "Date",
78 "Service-ID",
79 "Allow",
80 "Preview",
81 "Transfer-Preview",
82 "Transfer-Ignore",
83 "Transfer-Complete"
84 };
85
86
87 const struct ci_error_code ci_error_codes[] = {
88 {100, "Continue"}, /*Continue after ICAP Preview */
89 {200, "OK"},
90 {204, "Unmodified"}, /*No modifications needed */
91 {206, "Partial Content"}, /*Partial content modification*/
92 {400, "Bad request"}, /*Bad request */
93 {401, "Unauthorized"},
94 {403, "Forbidden"},
95 {404, "Service not found"}, /*ICAP Service not found */
96 {405, "Not allowed"}, /*Method not allowed for service (e.g., RESPMOD requested for
97 service that supports only REQMOD). */
98 {407, "Authentication Required"},
99 {408, "Request timeout"}, /*Request timeout. ICAP server gave up waiting for a request
100 from an ICAP client */
101 {500, "Server error"}, /*Server error. Error on the ICAP server, such as "out of disk
102 space" */
103 {501, "Not implemented"}, /*Method not implemented. This response is illegal for an
104 OPTIONS request since implementation of OPTIONS is mandatory. */
105 {502, "Bad Gateway"}, /*Bad Gateway. This is an ICAP proxy and proxying produced an
106 error. */
107 {503, "Service overloaded"}, /*Service overloaded. The ICAP server has exceeded a maximum
108 connection limit associated with this service; the ICAP client
109 should not exceed this limit in the future. */
110 {505, "Unsupported version"} /*ICAP version not supported by server. */
111 };
112
113 /*
114 #ifdef __CYGWIN__
115 int ci_error_code(int ec){
116 return (ec >= EC_100 && ec < EC_MAX ? ci_error_codes[ec].code:1000);
117 }
118
119 const char *unknownerrorcode = "UNKNOWN ERROR CODE";
120
121 const char *ci_error_code_string(int ec){
122 return (ec >= EC_100 && ec < EC_MAX?ci_error_codes[ec].str:unknownerrorcode);
123 }
124 #endif
125 */
126
127
128 const char *ci_encaps_entities[] = {
129 "req-hdr",
130 "res-hdr",
131 "req-body",
132 "res-body",
133 "null-body",
134 "opt-body"
135 };
136
137 #ifdef __CYGWIN__
138
139 const char *unknownentity = "UNKNOWN";
140 const char *unknownmethod = "UNKNOWN";
141
ci_method_string(int method)142 const char *ci_method_string(int method)
143 {
144 return (method <= ICAP_RESPMOD
145 && method >= ICAP_OPTIONS ? CI_Methods[method] : unknownmethod);
146 }
147
148
ci_encaps_entity_string(int e)149 const char *ci_encaps_entity_string(int e)
150 {
151 return (e <= ICAP_OPT_BODY
152 && e >= ICAP_REQ_HDR ? CI_EncapsEntities[e] : unknownentity);
153 }
154 #endif
155
ci_headers_create()156 ci_headers_list_t *ci_headers_create()
157 {
158 ci_headers_list_t *h;
159 h = malloc(sizeof(ci_headers_list_t));
160 if (!h) {
161 ci_debug_printf(1, "Error allocation memory for ci_headers_list_t (header.c: ci_headers_create)\n");
162 return NULL;
163 }
164 h->headers = NULL;
165 h->buf = NULL;
166 if (!(h->headers = malloc(HEADERSTARTSIZE * sizeof(char *)))
167 || !(h->buf = malloc(HEADSBUFSIZE * sizeof(char)))) {
168 ci_debug_printf(1, "Server Error: Error allocation memory \n");
169 if (h->headers)
170 free(h->headers);
171 if (h->buf)
172 free(h->buf);
173 free(h);
174 return NULL;
175 }
176
177 h->size = HEADERSTARTSIZE;
178 h->used = 0;
179 h->bufsize = HEADSBUFSIZE;
180 h->bufused = 0;
181 h->packed = 0;
182
183 return h;
184 }
185
ci_headers_destroy(ci_headers_list_t * h)186 void ci_headers_destroy(ci_headers_list_t * h)
187 {
188 free(h->headers);
189 free(h->buf);
190 free(h);
191 }
192
193
194
ci_headers_setsize(ci_headers_list_t * h,int size)195 int ci_headers_setsize(ci_headers_list_t * h, int size)
196 {
197 char *newbuf;
198 int new_size;
199 if (size < h->bufsize)
200 return 1;
201 /*Allocate buffer of size multiple of HEADSBUFSIZE */
202 new_size = (size / HEADSBUFSIZE + 1) * HEADSBUFSIZE;
203 newbuf = realloc(h->buf, new_size * sizeof(char));
204 if (!newbuf) {
205 ci_debug_printf(1, "Server Error:Error allocation memory \n");
206 return 0;
207 }
208 h->buf = newbuf;
209 h->bufsize = new_size;
210 return 1;
211 }
212
ci_headers_reset(ci_headers_list_t * h)213 void ci_headers_reset(ci_headers_list_t * h)
214 {
215 h->packed = 0;
216 h->used = 0;
217 h->bufused = 0;
218 }
219
ci_headers_add(ci_headers_list_t * h,const char * line)220 const char *ci_headers_add(ci_headers_list_t * h, const char *line)
221 {
222 char *newhead, **newspace, *newbuf;
223 int len, linelen;
224 int i = 0;
225
226 if (h->packed) { /*Not in edit mode*/
227 return NULL;
228 }
229
230 if (h->used == h->size) {
231 len = h->size + HEADERSTARTSIZE;
232 newspace = realloc(h->headers, len * sizeof(char *));
233 if (!newspace) {
234 ci_debug_printf(1, "Server Error:Error allocation memory \n");
235 return NULL;
236 }
237 h->headers = newspace;
238 h->size = len;
239 }
240 linelen = strlen(line);
241 len = h->bufsize;
242 while ( len - h->bufused < linelen + 4 )
243 len += HEADSBUFSIZE;
244 if (len > h->bufsize) {
245 newbuf = realloc(h->buf, len * sizeof(char));
246 if (!newbuf) {
247 ci_debug_printf(1, "Server Error:Error allocation memory \n");
248 return NULL;
249 }
250 h->buf = newbuf;
251 h->bufsize = len;
252 h->headers[0] = h->buf;
253 for (i = 1; i < h->used; i++)
254 h->headers[i] = h->headers[i - 1] + strlen(h->headers[i - 1]) + 2;
255 }
256 newhead = h->buf + h->bufused;
257 memcpy(newhead, line, linelen);
258 newhead[linelen] = '\0';
259 h->bufused += linelen + 2; //2 char size for \r\n at the end of each header
260 *(newhead + linelen + 1) = '\n';
261 *(newhead + linelen + 3) = '\n';
262 if (newhead)
263 h->headers[h->used++] = newhead;
264
265 return newhead;
266 }
267
268
ci_headers_addheaders(ci_headers_list_t * h,const ci_headers_list_t * headers)269 int ci_headers_addheaders(ci_headers_list_t * h, const ci_headers_list_t * headers)
270 {
271 int len, i;
272 char *newbuf, **newspace;
273
274 if (h->packed) { /*Not in edit mode*/
275 return 0;
276 }
277 len = h->size;
278 while ( len - h->used < headers->used )
279 len += HEADERSTARTSIZE;
280
281 if ( len > h->size ) {
282 newspace = realloc(h->headers, len * sizeof(char *));
283 if (!newspace) {
284 ci_debug_printf(1, "Server Error: Error allocating memory \n");
285 return 0;
286 }
287 h->headers = newspace;
288 h->size = len;
289 }
290
291 len = h->bufsize;
292 while (len - h->bufused < headers->bufused + 2)
293 len += HEADSBUFSIZE;
294 if (len > h->bufsize) {
295 newbuf = realloc(h->buf, len * sizeof(char));
296 if (!newbuf) {
297 ci_debug_printf(1, "Server Error: Error allocating memory \n");
298 return 0;
299 }
300 h->buf = newbuf;
301 h->bufsize = len;
302 }
303
304 memcpy(h->buf + h->bufused, headers->buf, headers->bufused + 2);
305
306 h->bufused += headers->bufused;
307 h->used += headers->used;
308
309 h->headers[0] = h->buf;
310 for (i = 1; i < h->used; i++)
311 h->headers[i] = h->headers[i - 1] + strlen(h->headers[i - 1]) + 2;
312 return 1;
313 }
314
ci_headers_first_line2(ci_headers_list_t * h,size_t * return_size)315 const char *ci_headers_first_line2(ci_headers_list_t *h, size_t *return_size)
316 {
317 const char *eol;
318 if (h->used == 0)
319 return NULL;
320
321 eol = h->used > 1 ? (h->headers[1] - 1) : (h->buf + h->bufused);
322 while ((eol > h->buf) && (*eol == '\0' || *eol == '\r' || *eol == '\n')) --eol;
323 *return_size = eol - h->buf + 1;
324
325 return h->buf;
326 }
327
ci_headers_first_line(ci_headers_list_t * h)328 const char *ci_headers_first_line(ci_headers_list_t *h)
329 {
330 if (h->used == 0)
331 return NULL;
332 return h->buf;
333 }
334
do_header_search(ci_headers_list_t * h,const char * header,const char ** value,const char ** end)335 static const char *do_header_search(ci_headers_list_t * h, const char *header, const char **value, const char **end)
336 {
337 int i;
338 size_t header_size = strlen(header);
339 const char *h_end = (h->buf + h->bufused);
340 const char *check_head, *lval;
341
342 if (!header_size)
343 return NULL;
344
345 for (i = 0; i < h->used; i++) {
346 check_head = h->headers[i];
347 if (h_end < check_head + header_size)
348 return NULL;
349 if (*(check_head + header_size) != ':')
350 continue;
351 if (strncasecmp(check_head, header, header_size) == 0) {
352 lval = check_head + header_size + 1;
353 if (value) {
354 while (lval <= h_end && (*lval == ' ' || *lval == '\t'))
355 ++(lval);
356 *value = lval;
357 }
358 if (end) {
359 *end = (i < h->used -1) ? (h->headers[i + 1] - 1) : (h->buf + h->bufused - 1);
360 if (*end < lval) /*parse error in headers ?*/
361 return NULL;
362 while ((*end > lval) && (**end == '\0' || **end == '\r' || **end == '\n')) --(*end);
363 }
364 return check_head;
365 }
366 }
367 return NULL;
368 }
369
ci_headers_search(ci_headers_list_t * h,const char * header)370 const char *ci_headers_search(ci_headers_list_t * h, const char *header)
371 {
372 return do_header_search(h, header, NULL, NULL);
373 }
374
ci_headers_search2(ci_headers_list_t * h,const char * header,size_t * return_size)375 const char *ci_headers_search2(ci_headers_list_t * h, const char *header, size_t *return_size)
376 {
377 const char *phead, *pend = NULL;
378 if ((phead = do_header_search(h, header, NULL, &pend))) {
379 *return_size = (pend != NULL) ? (pend - phead + 1) : 0;
380 return phead;
381 }
382 *return_size = 0;
383 return NULL;
384 }
385
ci_headers_value(ci_headers_list_t * h,const char * header)386 const char *ci_headers_value(ci_headers_list_t * h, const char *header)
387 {
388 const char *pval, *phead;
389 pval = NULL;
390 if ((phead = do_header_search(h, header, &pval, NULL)))
391 return pval;
392 return NULL;
393 }
394
ci_headers_value2(ci_headers_list_t * h,const char * header,size_t * return_size)395 const char *ci_headers_value2(ci_headers_list_t * h, const char *header, size_t *return_size)
396 {
397 const char *pval, *phead, *pend = NULL;
398 pval = NULL;
399 if ((phead = do_header_search(h, header, &pval, &pend))) {
400 *return_size = (pend != NULL) ? (pend - pval + 1) : 0;
401 return pval;
402 }
403 return NULL;
404 }
405
ci_headers_copy_value(ci_headers_list_t * h,const char * header,char * buf,size_t len)406 const char *ci_headers_copy_value(ci_headers_list_t * h, const char *header, char *buf, size_t len)
407 {
408 const char *phead = NULL, *pval = NULL, *pend = NULL;
409 char *dest, *dest_end;
410 phead = do_header_search(h, header, &pval, &pend);
411 if (phead == NULL || pval == NULL || pend == NULL)
412 return NULL;
413
414 /*skip spaces at the beginning*/
415 while (isspace(*pval) && pval < pend)
416 pval++;
417 while (isspace(*pend) && pend > pval)
418 pend--;
419
420 /*copy value to buf*/
421 dest = buf;
422 dest_end = buf + len -1;
423 for (; dest < dest_end && pval <= pend; dest++, pval++)
424 *dest = *pval;
425 *dest = '\0';
426 return buf;
427 }
428
ci_headers_remove(ci_headers_list_t * h,const char * header)429 int ci_headers_remove(ci_headers_list_t * h, const char *header)
430 {
431 const char *h_end;
432 char *phead;
433 int i, j, cur_head_size, rest_len;
434 size_t header_size;
435
436 if (h->packed) { /*Not in edit mode*/
437 return 0;
438 }
439
440 h_end = (h->buf + h->bufused);
441 header_size = strlen(header);
442 for (i = 0; i < h->used; i++) {
443 phead = h->headers[i];
444 if (h_end < phead + header_size)
445 return 0;
446 if (*(phead + header_size) != ':')
447 continue;
448 if (strncasecmp(phead, header, header_size) == 0) {
449 /*remove it........ */
450 if (i == h->used - 1) {
451 phead = h->headers[i];
452 *phead = '\r';
453 *(phead + 1) = '\n';
454 h->bufused = (phead - h->buf);
455 (h->used)--;
456 return 1;
457 } else {
458 cur_head_size = h->headers[i + 1] - h->headers[i];
459 rest_len =
460 h->bufused - (h->headers[i] - h->buf) - cur_head_size;
461 ci_debug_printf(5, "remove_header : remain len %d\n",
462 rest_len);
463 memmove(phead, h->headers[i + 1], rest_len);
464 /*reconstruct index..... */
465 h->bufused -= cur_head_size;
466 (h->used)--;
467 for (j = i + 1; j < h->used; j++) {
468 cur_head_size = strlen(h->headers[j - 1]);
469 h->headers[j] = h->headers[j - 1] + cur_head_size + 1;
470 if (h->headers[j][0] == '\n')
471 (h->headers[j])++;
472 }
473
474 return 1;
475 }
476 }
477 }
478 return 0;
479 }
480
ci_headers_replace(ci_headers_list_t * h,const char * header,const char * newval)481 const char *ci_headers_replace(ci_headers_list_t * h, const char *header, const char *newval)
482 {
483 if (h->packed) /*Not in edit mode*/
484 return NULL;
485
486 return NULL;
487 }
488
489 #define eoh(s) ((*s == '\r' && *(s+1) == '\n' && *(s+2) != '\t' && *(s+2) != ' ') || (*s == '\n' && *(s+1) != '\t' && *(s+1) != ' '))
490
ci_headers_iterate(ci_headers_list_t * h,void * data,void (* fn)(void *,const char * head,const char * value))491 int ci_headers_iterate(ci_headers_list_t * h, void *data, void (*fn)(void *, const char *head, const char *value))
492 {
493 char header[256];
494 char value[8196];
495 char *s;
496 int i, j;
497 for (i = 0; i < h->used; i++) {
498 s = h->headers[i];
499 for (j = 0; j < sizeof(header)-1 && *s != ':' && *s != ' ' && *s != '\0' && *s != '\r' && *s != '\n'; s++, j++)
500 header[j] = *s;
501 header[j] = '\0';
502 if (*s == ':') {
503 s++;
504 } else {
505 header[0] = '\0';
506 s = h->headers[i];
507 }
508 while (*s == ' ') s++;
509 for (j = 0; j < sizeof(value)-1 && *s != '\0' && !eoh(s); s++, j++)
510 value[j] = *s;
511 value[j] = '\0';
512 fn(data, header, value);
513 }
514 return 1;
515 }
516
ci_headers_pack(ci_headers_list_t * h)517 void ci_headers_pack(ci_headers_list_t * h)
518 {
519 /*Put the \r\n sequence at the end of each header before sending...... */
520 int i = 0, len = 0;
521 for (i = 0; i < h->used; i++) {
522 len = strlen(h->headers[i]);
523 if (h->headers[i][len + 1] == '\n') {
524 h->headers[i][len] = '\r';
525 /* h->headers[i][len+1] = '\n';*/
526 } else { /* handle the case that headers seperated with a '\n' only */
527 h->headers[i][len] = '\n';
528 }
529 }
530
531 if (h->buf[h->bufused + 1] == '\n') {
532 h->buf[h->bufused] = '\r';
533 /* h->buf[h->bufused+1] = '\n';*/
534 h->bufused += 2;
535 } else { /* handle the case that headers seperated with a '\n' only */
536 h->buf[h->bufused] = '\n';
537 h->bufused++;
538 }
539 h->packed = 1;
540 }
541
542
ci_headers_unpack(ci_headers_list_t * h)543 int ci_headers_unpack(ci_headers_list_t * h)
544 {
545 int len, eoh;
546 char **newspace;
547 char *ebuf, *str;
548
549 if (h->bufused < 2) /*???????????? */
550 return EC_400;
551
552 ebuf = h->buf + h->bufused - 2;
553 /* ebuf now must indicate the last \r\n so: */
554 if (*ebuf != '\r' && *ebuf != '\n') { /*Some sites return (this is bug ) a simple '\n' as end of header ..... */
555 ci_debug_printf(3,
556 "Parse error. The end chars are %c %c (%d %d) not the \\r \n",
557 *ebuf, *(ebuf + 1), (unsigned int) *ebuf,
558 (unsigned int) *(ebuf + 1));
559 return EC_400; /*Bad request .... */
560 }
561 *ebuf = '\0';
562
563 h->headers[0] = h->buf;
564 h->used = 1;
565
566 for (str = h->buf; str < ebuf; str++) { /*Construct index of headers */
567 eoh = 0;
568
569 if ((*str == '\r' && *(str + 1) == '\n')) {
570 if ((str + 2) >= ebuf
571 || (*(str + 2) != '\t' && *(str + 2) != ' '))
572 eoh = 1;
573 } else if (*str == '\n' && *(str + 1) != '\t' && *(str + 1) != ' ') {
574 /*handle the case that headers seperated with a '\n' only */
575 eoh = 1;
576 } else if (*str == '\0') /*Then we have a problem. This char is important for us. Yes can happen! */
577 *str = ' ';
578
579 if (eoh) {
580 *str = '\0';
581 if (h->size <= h->used) { /* Resize the headers index space ........ */
582 len = h->size + HEADERSTARTSIZE;
583 newspace = realloc(h->headers, len * sizeof(char *));
584 if (!newspace) {
585 ci_debug_printf(1,
586 "Server Error: Error allocating memory \n");
587 return EC_500;
588 }
589 h->headers = newspace;
590 h->size = len;
591 }
592 str++;
593 if (*str == '\n')
594 str++; /* handle the case that headers seperated with a '\n' only */
595 h->headers[h->used] = str;
596 h->used++;
597 }
598 }
599 h->packed = 0;
600 /*OK headers index construction ...... */
601 return EC_100;
602 }
603
ci_headers_pack_to_buffer(ci_headers_list_t * heads,char * buf,size_t size)604 size_t ci_headers_pack_to_buffer(ci_headers_list_t *heads, char *buf, size_t size)
605 {
606 size_t n;
607 int i;
608 char *pos;
609
610 n = heads->bufused;
611 if (!heads->packed)
612 n += 2;
613
614 if (n > size)
615 return 0;
616
617 memcpy(buf, heads->buf, heads->bufused);
618
619 if (!heads->packed) {
620 pos = buf;
621 for (i = 0; i < heads->used; ++i) {
622 pos = strchr(pos, '\0');
623 if (pos[1] == '\n')
624 pos[0] = '\r';
625 else
626 pos[0] = '\n';
627 }
628 buf[heads->bufused] = '\r';
629 buf[heads->bufused+1] = '\n';
630 }
631 return n;
632 }
633
634 /********************************************************************************************/
635 /* Entities List */
636
637
mk_encaps_entity(int type,int val)638 ci_encaps_entity_t *mk_encaps_entity(int type, int val)
639 {
640 ci_encaps_entity_t *h;
641 h = malloc(sizeof(ci_encaps_entity_t));
642 if (!h)
643 return NULL;
644
645 h->start = val;
646 h->type = type;
647 if (type == ICAP_REQ_HDR || type == ICAP_RES_HDR)
648 h->entity = ci_headers_create();
649 else
650 h->entity = NULL;
651 return h;
652 }
653
destroy_encaps_entity(ci_encaps_entity_t * e)654 void destroy_encaps_entity(ci_encaps_entity_t * e)
655 {
656 if (e->type == ICAP_REQ_HDR || e->type == ICAP_RES_HDR) {
657 ci_headers_destroy((ci_headers_list_t *) e->entity);
658 } else
659 free(e->entity);
660 free(e);
661 }
662
get_encaps_type(const char * buf,int * val,char ** endpoint)663 int get_encaps_type(const char *buf, int *val, char **endpoint)
664 {
665
666 if (0 == strncmp(buf, "req-hdr", 7)) {
667 *val = strtol(buf + 8, endpoint, 10);
668 return ICAP_REQ_HDR;
669 }
670 if (0 == strncmp(buf, "res-hdr", 7)) {
671 *val = strtol(buf + 8, endpoint, 10);
672 return ICAP_RES_HDR;
673 }
674 if (0 == strncmp(buf, "req-body", 8)) {
675 *val = strtol(buf + 9, endpoint, 10);
676 return ICAP_REQ_BODY;
677 }
678 if (0 == strncmp(buf, "res-body", 8)) {
679 *val = strtol(buf + 9, endpoint, 10);
680 return ICAP_RES_BODY;
681 }
682 if (0 == strncmp(buf, "null-body", 9)) {
683 *val = strtol(buf + 10, endpoint, 10);
684 return ICAP_NULL_BODY;
685 }
686 return -1;
687 }
688
689
sizeofheader(ci_headers_list_t * h)690 int sizeofheader(ci_headers_list_t * h)
691 {
692 /*
693 int size=0,i;
694 for(i=0;i<h->used;i++){
695 size+=strlen(h->headers[i])+2;
696 }
697 size+=2;
698 return size;
699 */
700 return h->bufused + 2;
701 }
702
sizeofencaps(ci_encaps_entity_t * e)703 int sizeofencaps(ci_encaps_entity_t * e)
704 {
705 if (e->type == ICAP_REQ_HDR || e->type == ICAP_RES_HDR) {
706 return sizeofheader((ci_headers_list_t *) e->entity);
707 }
708 return 0;
709 }
710