GNU libmicrohttpd  0.9.5
postprocessor.c
Go to the documentation of this file.
1 /*
2  This file is part of libmicrohttpd
3  (C) 2007, 2009, 2010, 2011, 2012 Daniel Pittman and Christian Grothoff
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.1 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 Free Software
17  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18 */
19 
26 #include "internal.h"
27 
33 #define XBUF_SIZE 512
34 
39 {
40  /* general states */
45 
46  /* url encoding-states */
49 
50  /* post encoding-states */
55 
56  /* nested post-encoding states */
62 
63 };
64 
66 {
71 
76  RN_OptN = 1,
77 
82  RN_Full = 2,
83 
88  RN_Dash = 3,
89 
93  RN_Dash2 = 4,
94 };
95 
102 {
103  NE_none = 0,
108 };
109 
114 struct MHD_PostProcessor
115 {
116 
121  struct MHD_Connection *connection;
122 
127 
131  void *cls;
132 
137  const char *encoding;
138 
142  const char *boundary;
143 
147  char *nested_boundary;
148 
152  char *content_name;
153 
157  char *content_type;
158 
162  char *content_filename;
163 
167  char *content_transfer_encoding;
168 
173  char xbuf[8];
174 
178  size_t buffer_size;
179 
183  size_t buffer_pos;
184 
188  size_t xbuf_pos;
189 
193  uint64_t value_offset;
194 
198  size_t blen;
199 
203  size_t nlen;
204 
208  enum PP_State state;
209 
216  enum RN_State skip_rn;
217 
222  enum PP_State dash_state;
223 
228  enum NE_State have;
229 
230 };
231 
232 
251 struct MHD_PostProcessor *
253  size_t buffer_size,
254  MHD_PostDataIterator ikvi, void *cls)
255 {
256  struct MHD_PostProcessor *ret;
257  const char *encoding;
258  const char *boundary;
259  size_t blen;
260 
261  if ((buffer_size < 256) || (connection == NULL) || (ikvi == NULL))
262  mhd_panic (mhd_panic_cls, __FILE__, __LINE__, NULL);
263  encoding = MHD_lookup_connection_value (connection,
266  if (encoding == NULL)
267  return NULL;
268  boundary = NULL;
269  if (0 != strncasecmp (MHD_HTTP_POST_ENCODING_FORM_URLENCODED, encoding,
271  {
272  if (0 !=
273  strncasecmp (MHD_HTTP_POST_ENCODING_MULTIPART_FORMDATA, encoding,
275  return NULL;
276  boundary =
277  &encoding[strlen (MHD_HTTP_POST_ENCODING_MULTIPART_FORMDATA)];
278  /* Q: should this be "strcasestr"? */
279  boundary = strstr (boundary, "boundary=");
280  if (NULL == boundary)
281  return NULL; /* failed to determine boundary */
282  boundary += strlen ("boundary=");
283  blen = strlen (boundary);
284  if ((blen == 0) || (blen * 2 + 2 > buffer_size))
285  return NULL; /* (will be) out of memory or invalid boundary */
286  if ( (boundary[0] == '"') && (boundary[blen - 1] == '"') )
287  {
288  /* remove enclosing quotes */
289  ++boundary;
290  blen -= 2;
291  }
292  }
293  else
294  blen = 0;
295  ret = malloc (sizeof (struct MHD_PostProcessor) + buffer_size + 1);
296  if (ret == NULL)
297  return NULL;
298  memset (ret, 0, sizeof (struct MHD_PostProcessor) + buffer_size + 1);
299  ret->connection = connection;
300  ret->ikvi = ikvi;
301  ret->cls = cls;
302  ret->encoding = encoding;
303  ret->buffer_size = buffer_size;
304  ret->state = PP_Init;
305  ret->blen = blen;
306  ret->boundary = boundary;
307  ret->skip_rn = RN_Inactive;
308  return ret;
309 }
310 
314 static int
315 post_process_urlencoded (struct MHD_PostProcessor *pp,
316  const char *post_data,
317  size_t post_data_len)
318 {
319  size_t equals;
320  size_t amper;
321  size_t poff;
322  size_t xoff;
323  size_t delta;
324  int end_of_value_found;
325  char *buf;
326  char xbuf[XBUF_SIZE + 1];
327 
328  buf = (char *) &pp[1];
329  poff = 0;
330  while (poff < post_data_len)
331  {
332  switch (pp->state)
333  {
334  case PP_Error:
335  return MHD_NO;
336  case PP_Done:
337  /* did not expect to receive more data */
338  pp->state = PP_Error;
339  return MHD_NO;
340  case PP_Init:
341  equals = 0;
342  while ((equals + poff < post_data_len) &&
343  (post_data[equals + poff] != '='))
344  equals++;
345  if (equals + pp->buffer_pos > pp->buffer_size)
346  {
347  pp->state = PP_Error; /* out of memory */
348  return MHD_NO;
349  }
350  memcpy (&buf[pp->buffer_pos], &post_data[poff], equals);
351  pp->buffer_pos += equals;
352  if (equals + poff == post_data_len)
353  return MHD_YES; /* no '=' yet */
354  buf[pp->buffer_pos] = '\0'; /* 0-terminate key */
355  pp->buffer_pos = 0; /* reset for next key */
356  MHD_http_unescape (NULL, NULL, buf);
357  poff += equals + 1;
358  pp->state = PP_ProcessValue;
359  pp->value_offset = 0;
360  break;
361  case PP_ProcessValue:
362  /* obtain rest of value from previous iteration */
363  memcpy (xbuf, pp->xbuf, pp->xbuf_pos);
364  xoff = pp->xbuf_pos;
365  pp->xbuf_pos = 0;
366 
367  /* find last position in input buffer that is part of the value */
368  amper = 0;
369  while ((amper + poff < post_data_len) &&
370  (amper < XBUF_SIZE) &&
371  (post_data[amper + poff] != '&') &&
372  (post_data[amper + poff] != '\n') &&
373  (post_data[amper + poff] != '\r'))
374  amper++;
375  end_of_value_found = ((amper + poff < post_data_len) &&
376  ((post_data[amper + poff] == '&') ||
377  (post_data[amper + poff] == '\n') ||
378  (post_data[amper + poff] == '\r')));
379  /* compute delta, the maximum number of bytes that we will be able to
380  process right now (either amper-limited of xbuf-size limited) */
381  delta = amper;
382  if (delta > XBUF_SIZE - xoff)
383  delta = XBUF_SIZE - xoff;
384 
385  /* move input into processing buffer */
386  memcpy (&xbuf[xoff], &post_data[poff], delta);
387  xoff += delta;
388  poff += delta;
389 
390  /* find if escape sequence is at the end of the processing buffer;
391  if so, exclude those from processing (reduce delta to point at
392  end of processed region) */
393  delta = xoff;
394  if ((delta > 0) && (xbuf[delta - 1] == '%'))
395  delta--;
396  else if ((delta > 1) && (xbuf[delta - 2] == '%'))
397  delta -= 2;
398 
399  /* if we have an incomplete escape sequence, save it to
400  pp->xbuf for later */
401  if (delta < xoff)
402  {
403  memcpy (pp->xbuf, &xbuf[delta], xoff - delta);
404  pp->xbuf_pos = xoff - delta;
405  xoff = delta;
406  }
407 
408  /* If we have nothing to do (delta == 0) and
409  not just because the value is empty (are
410  waiting for more data), go for next iteration */
411  if ((xoff == 0) && (poff == post_data_len))
412  continue;
413 
414  /* unescape */
415  xbuf[xoff] = '\0'; /* 0-terminate in preparation */
416  xoff = MHD_http_unescape (NULL, NULL, xbuf);
417  /* finally: call application! */
418  if (MHD_NO == pp->ikvi (pp->cls, MHD_POSTDATA_KIND, (const char *) &pp[1], /* key */
419  NULL, NULL, NULL, xbuf, pp->value_offset,
420  xoff))
421  {
422  pp->state = PP_Error;
423  return MHD_NO;
424  }
425  pp->value_offset += xoff;
426 
427  /* are we done with the value? */
428  if (end_of_value_found)
429  {
430  /* we found the end of the value! */
431  if ((post_data[poff] == '\n') || (post_data[poff] == '\r'))
432  {
433  pp->state = PP_ExpectNewLine;
434  }
435  else
436  {
437  poff++; /* skip '&' */
438  pp->state = PP_Init;
439  }
440  }
441  break;
442  case PP_ExpectNewLine:
443  if ((post_data[poff] == '\n') || (post_data[poff] == '\r'))
444  {
445  poff++;
446  /* we are done, report error if we receive any more... */
447  pp->state = PP_Done;
448  return MHD_YES;
449  }
450  return MHD_NO;
451  default:
452  mhd_panic (mhd_panic_cls, __FILE__, __LINE__, NULL); /* should never happen! */
453  }
454  }
455  return MHD_YES;
456 }
457 
464 static int
465 try_match_header (const char *prefix, char *line, char **suffix)
466 {
467  if (NULL != *suffix)
468  return MHD_NO;
469  while (*line != 0)
470  {
471  if (0 == strncasecmp (prefix, line, strlen (prefix)))
472  {
473  *suffix = strdup (&line[strlen (prefix)]);
474  return MHD_YES;
475  }
476  ++line;
477  }
478  return MHD_NO;
479 }
480 
481 static int
482 find_boundary (struct MHD_PostProcessor *pp,
483  const char *boundary,
484  size_t blen,
485  size_t *ioffptr,
486  enum PP_State next_state, enum PP_State next_dash_state)
487 {
488  char *buf = (char *) &pp[1];
489 
490  if (pp->buffer_pos < 2 + blen)
491  {
492  if (pp->buffer_pos == pp->buffer_size)
493  pp->state = PP_Error; /* out of memory */
494  return MHD_NO; /* not enough data */
495  }
496  if ((0 != memcmp ("--", buf, 2)) || (0 != memcmp (&buf[2], boundary, blen)))
497  {
498  if (pp->state != PP_Init)
499  pp->state = PP_Error;
500  return MHD_NO; /* expected boundary */
501  }
502  /* remove boundary from buffer */
503  (*ioffptr) += 2 + blen;
504  /* next: start with headers */
505  pp->skip_rn = RN_Dash;
506  pp->state = next_state;
507  pp->dash_state = next_dash_state;
508  return MHD_YES;
509 }
510 
519 static void
520 try_get_value (const char *buf, const char *key, char **destination)
521 {
522  const char *spos;
523  const char *bpos;
524  const char *endv;
525  size_t klen;
526  size_t vlen;
527 
528  if (NULL != *destination)
529  return;
530  bpos = buf;
531  klen = strlen (key);
532  while (NULL != (spos = strstr (bpos, key)))
533  {
534  if ((spos[klen] != '=') || ((spos != buf) && (spos[-1] != ' ')))
535  {
536  /* no match */
537  bpos = spos + 1;
538  continue;
539  }
540  if (spos[klen + 1] != '"')
541  return; /* not quoted */
542  if (NULL == (endv = strchr (&spos[klen + 2], '\"')))
543  return; /* no end-quote */
544  vlen = endv - spos - klen - 1;
545  *destination = malloc (vlen);
546  if (NULL == *destination)
547  return; /* out of memory */
548  (*destination)[vlen - 1] = '\0';
549  memcpy (*destination, &spos[klen + 2], vlen - 1);
550  return; /* success */
551  }
552 }
553 
566 static int
567 process_multipart_headers (struct MHD_PostProcessor *pp,
568  size_t *ioffptr, enum PP_State next_state)
569 {
570  char *buf = (char *) &pp[1];
571  size_t newline;
572 
573  newline = 0;
574  while ((newline < pp->buffer_pos) &&
575  (buf[newline] != '\r') && (buf[newline] != '\n'))
576  newline++;
577  if (newline == pp->buffer_size)
578  {
579  pp->state = PP_Error;
580  return MHD_NO; /* out of memory */
581  }
582  if (newline == pp->buffer_pos)
583  return MHD_NO; /* will need more data */
584  if (newline == 0)
585  {
586  /* empty line - end of headers */
587  pp->skip_rn = RN_Full;
588  pp->state = next_state;
589  return MHD_YES;
590  }
591  /* got an actual header */
592  if (buf[newline] == '\r')
593  pp->skip_rn = RN_OptN;
594  buf[newline] = '\0';
595  if (0 == strncasecmp ("Content-disposition: ",
596  buf, strlen ("Content-disposition: ")))
597  {
598  try_get_value (&buf[strlen ("Content-disposition: ")],
599  "name", &pp->content_name);
600  try_get_value (&buf[strlen ("Content-disposition: ")],
601  "filename", &pp->content_filename);
602  }
603  else
604  {
605  try_match_header ("Content-type: ", buf, &pp->content_type);
606  try_match_header ("Content-Transfer-Encoding: ",
607  buf, &pp->content_transfer_encoding);
608  }
609  (*ioffptr) += newline + 1;
610  return MHD_YES;
611 }
612 
627 static int
628 process_value_to_boundary (struct MHD_PostProcessor *pp,
629  size_t *ioffptr,
630  const char *boundary,
631  size_t blen,
632  enum PP_State next_state,
633  enum PP_State next_dash_state)
634 {
635  char *buf = (char *) &pp[1];
636  size_t newline;
637 
638  /* all data in buf until the boundary
639  (\r\n--+boundary) is part of the value */
640  newline = 0;
641  while (1)
642  {
643  while ((newline + 4 < pp->buffer_pos) &&
644  (0 != memcmp ("\r\n--", &buf[newline], 4)))
645  newline++;
646  if (newline + pp->blen + 4 <= pp->buffer_pos)
647  {
648  /* can check boundary */
649  if (0 != memcmp (&buf[newline + 4], boundary, pp->blen))
650  {
651  /* no boundary, "\r\n--" is part of content, skip */
652  newline += 4;
653  continue;
654  }
655  else
656  {
657  /* boundary found, process until newline then
658  skip boundary and go back to init */
659  pp->skip_rn = RN_Dash;
660  pp->state = next_state;
661  pp->dash_state = next_dash_state;
662  (*ioffptr) += pp->blen + 4; /* skip boundary as well */
663  buf[newline] = '\0';
664  break;
665  }
666  }
667  else
668  {
669  /* cannot check for boundary, process content that
670  we have and check again later; except, if we have
671  no content, abort (out of memory) */
672  if ((newline == 0) && (pp->buffer_pos == pp->buffer_size))
673  {
674  pp->state = PP_Error;
675  return MHD_NO;
676  }
677  break;
678  }
679  }
680  /* newline is either at beginning of boundary or
681  at least at the last character that we are sure
682  is not part of the boundary */
683  if (MHD_NO == pp->ikvi (pp->cls,
685  pp->content_name,
686  pp->content_filename,
687  pp->content_type,
688  pp->content_transfer_encoding,
689  buf, pp->value_offset, newline))
690  {
691  pp->state = PP_Error;
692  return MHD_NO;
693  }
694  pp->value_offset += newline;
695  (*ioffptr) += newline;
696  return MHD_YES;
697 }
698 
699 static void
700 free_unmarked (struct MHD_PostProcessor *pp)
701 {
702  if ((pp->content_name != NULL) && (0 == (pp->have & NE_content_name)))
703  {
704  free (pp->content_name);
705  pp->content_name = NULL;
706  }
707  if ((pp->content_type != NULL) && (0 == (pp->have & NE_content_type)))
708  {
709  free (pp->content_type);
710  pp->content_type = NULL;
711  }
712  if ((pp->content_filename != NULL) &&
713  (0 == (pp->have & NE_content_filename)))
714  {
715  free (pp->content_filename);
716  pp->content_filename = NULL;
717  }
718  if ((pp->content_transfer_encoding != NULL) &&
719  (0 == (pp->have & NE_content_transfer_encoding)))
720  {
721  free (pp->content_transfer_encoding);
722  pp->content_transfer_encoding = NULL;
723  }
724 }
725 
729 static int
730 post_process_multipart (struct MHD_PostProcessor *pp,
731  const char *post_data,
732  size_t post_data_len)
733 {
734  char *buf;
735  size_t max;
736  size_t ioff;
737  size_t poff;
738  int state_changed;
739 
740  buf = (char *) &pp[1];
741  ioff = 0;
742  poff = 0;
743  state_changed = 1;
744  while ((poff < post_data_len) ||
745  ((pp->buffer_pos > 0) && (state_changed != 0)))
746  {
747  /* first, move as much input data
748  as possible to our internal buffer */
749  max = pp->buffer_size - pp->buffer_pos;
750  if (max > post_data_len - poff)
751  max = post_data_len - poff;
752  memcpy (&buf[pp->buffer_pos], &post_data[poff], max);
753  poff += max;
754  pp->buffer_pos += max;
755  if ((max == 0) && (state_changed == 0) && (poff < post_data_len))
756  {
757  pp->state = PP_Error;
758  return MHD_NO; /* out of memory */
759  }
760  state_changed = 0;
761 
762  /* first state machine for '\r'-'\n' and '--' handling */
763  switch (pp->skip_rn)
764  {
765  case RN_Inactive:
766  break;
767  case RN_OptN:
768  if (buf[0] == '\n')
769  {
770  ioff++;
771  pp->skip_rn = RN_Inactive;
772  goto AGAIN;
773  }
774  /* fall-through! */
775  case RN_Dash:
776  if (buf[0] == '-')
777  {
778  ioff++;
779  pp->skip_rn = RN_Dash2;
780  goto AGAIN;
781  }
782  pp->skip_rn = RN_Full;
783  /* fall-through! */
784  case RN_Full:
785  if (buf[0] == '\r')
786  {
787  if ((pp->buffer_pos > 1) && (buf[1] == '\n'))
788  {
789  pp->skip_rn = RN_Inactive;
790  ioff += 2;
791  }
792  else
793  {
794  pp->skip_rn = RN_OptN;
795  ioff++;
796  }
797  goto AGAIN;
798  }
799  if (buf[0] == '\n')
800  {
801  ioff++;
802  pp->skip_rn = RN_Inactive;
803  goto AGAIN;
804  }
805  pp->skip_rn = RN_Inactive;
806  pp->state = PP_Error;
807  return MHD_NO; /* no '\r\n' */
808  case RN_Dash2:
809  if (buf[0] == '-')
810  {
811  ioff++;
812  pp->skip_rn = RN_Full;
813  pp->state = pp->dash_state;
814  goto AGAIN;
815  }
816  pp->state = PP_Error;
817  break;
818  }
819 
820  /* main state engine */
821  switch (pp->state)
822  {
823  case PP_Error:
824  return MHD_NO;
825  case PP_Done:
826  /* did not expect to receive more data */
827  pp->state = PP_Error;
828  return MHD_NO;
829  case PP_Init:
841  if (MHD_NO == find_boundary (pp,
842  pp->boundary,
843  pp->blen,
844  &ioff,
846  ++ioff;
847  break;
848  case PP_NextBoundary:
849  if (MHD_NO == find_boundary (pp,
850  pp->boundary,
851  pp->blen,
852  &ioff,
854  {
855  if (pp->state == PP_Error)
856  return MHD_NO;
857  goto END;
858  }
859  break;
861  if (MHD_NO ==
863  {
864  if (pp->state == PP_Error)
865  return MHD_NO;
866  else
867  goto END;
868  }
869  state_changed = 1;
870  break;
872  if ((pp->content_type != NULL) &&
873  (0 == strncasecmp (pp->content_type,
874  "multipart/mixed",
875  strlen ("multipart/mixed"))))
876  {
877  pp->nested_boundary = strstr (pp->content_type, "boundary=");
878  if (pp->nested_boundary == NULL)
879  {
880  pp->state = PP_Error;
881  return MHD_NO;
882  }
883  pp->nested_boundary =
884  strdup (&pp->nested_boundary[strlen ("boundary=")]);
885  if (pp->nested_boundary == NULL)
886  {
887  /* out of memory */
888  pp->state = PP_Error;
889  return MHD_NO;
890  }
891  /* free old content type, we will need that field
892  for the content type of the nested elements */
893  free (pp->content_type);
894  pp->content_type = NULL;
895  pp->nlen = strlen (pp->nested_boundary);
896  pp->state = PP_Nested_Init;
897  state_changed = 1;
898  break;
899  }
900  pp->state = PP_ProcessValueToBoundary;
901  pp->value_offset = 0;
902  state_changed = 1;
903  break;
906  &ioff,
907  pp->boundary,
908  pp->blen,
910  PP_Done))
911  {
912  if (pp->state == PP_Error)
913  return MHD_NO;
914  break;
915  }
916  break;
917  case PP_PerformCleanup:
918  /* clean up state of one multipart form-data element! */
919  pp->have = NE_none;
920  free_unmarked (pp);
921  if (pp->nested_boundary != NULL)
922  {
923  free (pp->nested_boundary);
924  pp->nested_boundary = NULL;
925  }
926  pp->state = PP_ProcessEntryHeaders;
927  state_changed = 1;
928  break;
929  case PP_Nested_Init:
930  if (pp->nested_boundary == NULL)
931  {
932  pp->state = PP_Error;
933  return MHD_NO;
934  }
935  if (MHD_NO == find_boundary (pp,
936  pp->nested_boundary,
937  pp->nlen,
938  &ioff,
940  PP_NextBoundary /* or PP_Error? */ ))
941  {
942  if (pp->state == PP_Error)
943  return MHD_NO;
944  goto END;
945  }
946  break;
948  /* remember what headers were given
949  globally */
950  pp->have = NE_none;
951  if (pp->content_name != NULL)
952  pp->have |= NE_content_name;
953  if (pp->content_type != NULL)
954  pp->have |= NE_content_type;
955  if (pp->content_filename != NULL)
956  pp->have |= NE_content_filename;
957  if (pp->content_transfer_encoding != NULL)
958  pp->have |= NE_content_transfer_encoding;
959  pp->state = PP_Nested_ProcessEntryHeaders;
960  state_changed = 1;
961  break;
963  pp->value_offset = 0;
964  if (MHD_NO ==
965  process_multipart_headers (pp, &ioff,
967  {
968  if (pp->state == PP_Error)
969  return MHD_NO;
970  else
971  goto END;
972  }
973  state_changed = 1;
974  break;
977  &ioff,
978  pp->nested_boundary,
979  pp->nlen,
982  {
983  if (pp->state == PP_Error)
984  return MHD_NO;
985  break;
986  }
987  break;
989  free_unmarked (pp);
990  pp->state = PP_Nested_ProcessEntryHeaders;
991  state_changed = 1;
992  break;
993  default:
994  mhd_panic (mhd_panic_cls, __FILE__, __LINE__, NULL); /* should never happen! */
995  }
996  AGAIN:
997  if (ioff > 0)
998  {
999  memmove (buf, &buf[ioff], pp->buffer_pos - ioff);
1000  pp->buffer_pos -= ioff;
1001  ioff = 0;
1002  state_changed = 1;
1003  }
1004  }
1005 END:
1006  if (ioff != 0)
1007  {
1008  memmove (buf, &buf[ioff], pp->buffer_pos - ioff);
1009  pp->buffer_pos -= ioff;
1010  }
1011  if (poff < post_data_len)
1012  {
1013  pp->state = PP_Error;
1014  return MHD_NO; /* serious error */
1015  }
1016  return MHD_YES;
1017 }
1018 
1033 int
1034 MHD_post_process (struct MHD_PostProcessor *pp,
1035  const char *post_data, size_t post_data_len)
1036 {
1037  if (post_data_len == 0)
1038  return MHD_YES;
1039  if (pp == NULL)
1040  return MHD_NO;
1041  if (0 == strncasecmp (MHD_HTTP_POST_ENCODING_FORM_URLENCODED, pp->encoding,
1043  return post_process_urlencoded (pp, post_data, post_data_len);
1044  if (0 ==
1045  strncasecmp (MHD_HTTP_POST_ENCODING_MULTIPART_FORMDATA, pp->encoding,
1047  return post_process_multipart (pp, post_data, post_data_len);
1048  /* this should never be reached */
1049  return MHD_NO;
1050 }
1051 
1055 int
1056 MHD_destroy_post_processor (struct MHD_PostProcessor *pp)
1057 {
1058  int ret;
1059 
1060  if (NULL == pp)
1061  return MHD_YES;
1062  /* These internal strings need cleaning up since
1063  the post-processing may have been interrupted
1064  at any stage */
1065  if ((pp->xbuf_pos > 0) || (pp->state != PP_Done))
1066  ret = MHD_NO;
1067  else
1068  ret = MHD_YES;
1069  pp->have = NE_none;
1070  free_unmarked (pp);
1071  if (pp->nested_boundary != NULL)
1072  free (pp->nested_boundary);
1073  free (pp);
1074  return ret;
1075 }
1076 
1077 /* end of postprocessor.c */