- Added new function cgi_redirect to handle creation of
[privoxy.git] / pcrs.c
1 const char pcrs_rcs[] = "$Id: pcrs.c,v 1.19.2.2 2002/10/08 16:22:28 oes Exp $";
2
3 /*********************************************************************
4  *
5  * File        :  $Source: /cvsroot/ijbswa/current/Attic/pcrs.c,v $
6  *
7  * Purpose     :  pcrs is a supplement to the pcre library by Philip Hazel
8  *                <ph10@cam.ac.uk> and adds Perl-style substitution. That
9  *                is, it mimics Perl's 's' operator. See pcrs(3) for details.
10  *
11  *
12  * Copyright   :  Written and Copyright (C) 2000, 2001 by Andreas S. Oesterhelt
13  *                <andreas@oesterhelt.org>
14  *
15  *                This program is free software; you can redistribute it 
16  *                and/or modify it under the terms of the GNU Lesser
17  *                General Public License (LGPL), version 2.1, which  should
18  *                be included in this distribution (see LICENSE.txt), with
19  *                the exception that the permission to replace that license
20  *                with the GNU General Public License (GPL) given in section
21  *                3 is restricted to version 2 of the GPL.
22  *
23  *                This program is distributed in the hope that it will
24  *                be useful, but WITHOUT ANY WARRANTY; without even the
25  *                implied warranty of MERCHANTABILITY or FITNESS FOR A
26  *                PARTICULAR PURPOSE.  See the license for more details.
27  *
28  *                The GNU Lesser General Public License should be included
29  *                with this file.  If not, you can view it at
30  *                http://www.gnu.org/licenses/lgpl.html
31  *                or write to the Free Software Foundation, Inc., 59
32  *                Temple Place - Suite 330, Boston, MA  02111-1307, USA.
33  *
34  * Revisions   :
35  *    $Log: pcrs.c,v $
36  *    Revision 1.19.2.2  2002/10/08 16:22:28  oes
37  *    Bugfix: Need to check validity of backreferences explicitly,
38  *    because when max_matches are reached and matches is expanded,
39  *    realloc() does not zero the memory. Fixes Bug # 606227
40  *
41  *    Revision 1.19.2.1  2002/08/10 11:23:40  oes
42  *    Include prce.h via project.h, where the appropriate
43  *    source will have been selected
44  *
45  *    Revision 1.19  2002/03/08 14:47:48  oes
46  *    Cosmetics
47  *
48  *    Revision 1.18  2002/03/08 14:17:14  oes
49  *    Fixing -Wconversion warnings
50  *
51  *    Revision 1.17  2002/03/08 13:45:48  oes
52  *    Hiding internal functions
53  *
54  *    Revision 1.16  2001/11/30 21:32:14  jongfoster
55  *    Fixing signed/unsigned comparison (Andreas please check this!)
56  *    One tab->space
57  *
58  *    Revision 1.15  2001/09/20 16:11:06  steudten
59  *
60  *    Add casting for some string functions.
61  *
62  *    Revision 1.14  2001/09/09 21:41:57  oes
63  *    Fixing yet another silly bug
64  *
65  *    Revision 1.13  2001/09/06 14:05:59  oes
66  *    Fixed silly bug
67  *
68  *    Revision 1.12  2001/08/18 11:35:00  oes
69  *    - Introduced pcrs_strerror()
70  *    - made some NULL arguments non-fatal
71  *    - added support for \n \r \e \b \t \f \a \0 in substitute
72  *    - made quoting adhere to standard rules
73  *    - added warning for bad backrefs
74  *    - added pcrs_execute_list()
75  *    - fixed comments
76  *    - bugfix & cosmetics
77  *
78  *    Revision 1.11  2001/08/15 15:32:03  oes
79  *     - Added support for Perl's special variables $+, $' and $`
80  *     - Improved the substitute parser
81  *     - Replaced the hard limit for the maximum number of matches
82  *       by dynamic reallocation
83  *
84  *    Revision 1.10  2001/08/05 13:13:11  jongfoster
85  *    Making parameters "const" where possible.
86  *
87  *    Revision 1.9  2001/07/18 17:27:00  oes
88  *    Changed interface; Cosmetics
89  *
90  *    Revision 1.8  2001/06/29 21:45:41  oes
91  *    Indentation, CRLF->LF, Tab-> Space
92  *
93  *    Revision 1.7  2001/06/29 13:33:04  oes
94  *    - Cleaned up, renamed and reordered functions,
95  *      improved comments
96  *    - Removed my_strsep
97  *    - Replaced globalflag with a general flags int
98  *      that holds PCRS_GLOBAL, PCRS_SUCCESS, and PCRS_TRIVIAL
99  *    - Introduced trivial option that will prevent pcrs
100  *      from honouring backreferences in the substitute,
101  *      which is useful for large substitutes that are
102  *      red in from somewhere and saves the pain of escaping
103  *      the backrefs
104  *    - Introduced convenience function pcrs_free_joblist()
105  *    - Split pcrs_make_job() into pcrs_compile(), which still
106  *      takes a complete s/// comand as argument and parses it,
107  *      and a new function pcrs_make_job, which takes the
108  *      three separate components. This should make for a
109  *      much friendlier frontend.
110  *    - Removed create_pcrs_job() which was useless
111  *    - Fixed a bug in pcrs_execute
112  *    - Success flag is now handled by pcrs instead of user
113  *
114  *    Revision 1.6  2001/06/03 19:12:45  oes
115  *    added FIXME
116  *
117  *    Revision 1.5  2001/05/29 09:50:24  jongfoster
118  *    (Fixed one int -> size_t)
119  *
120  *    Revision 1.4  2001/05/25 14:12:40  oes
121  *    Fixed bug: Empty substitutes now detected
122  *
123  *    Revision 1.3  2001/05/25 11:03:55  oes
124  *    Added sanity check for NULL jobs to pcrs_exec_substitution
125  *
126  *    Revision 1.2  2001/05/22 18:46:04  oes
127  *
128  *      Added support for PCRE_UNGREEDY behaviour to pcrs,
129  *      which is selected by the (nonstandard and therefore
130  *      capital) letter 'U' in the option string.
131  *      It causes the quantifiers to be ungreedy by default.
132  *      Appending a ? turns back to greedy (!).
133  *
134  *    Revision 1.1.1.1  2001/05/15 13:59:02  oes
135  *    Initial import of version 2.9.3 source tree
136  *
137  *
138  *********************************************************************/
139 \f
140
141 /*
142  * Include project.h just so that the right pcre.h gets
143  * included from there
144  */
145 #include "project.h"
146
147 #include <string.h>
148 #include <ctype.h>
149
150 #include "pcrs.h"
151
152 const char pcrs_h_rcs[] = PCRS_H_VERSION;
153
154 /*
155  * Internal prototypes
156  */
157
158 static int              pcrs_parse_perl_options(const char *optstring, int *flags);
159 static pcrs_substitute *pcrs_compile_replacement(const char *replacement, int trivialflag,
160                         int capturecount, int *errptr);
161
162 /*********************************************************************
163  *
164  * Function    :  pcrs_strerror
165  *
166  * Description :  Return a string describing a given error code.
167  *             
168  * Parameters  :
169  *          1  :  error = the error code
170  *
171  * Returns     :  char * to the descriptive string
172  *
173  *********************************************************************/
174 const char *pcrs_strerror(const int error)
175 {
176    if (error < 0)
177    {
178       switch (error)
179       {
180          /* Passed-through PCRE error: */
181          case PCRE_ERROR_NOMEMORY:     return "(pcre:) No memory";
182
183          /* Shouldn't happen unless PCRE or PCRS bug, or user messed with compiled job: */
184          case PCRE_ERROR_NULL:         return "(pcre:) NULL code or subject or ovector";
185          case PCRE_ERROR_BADOPTION:    return "(pcre:) Unrecognized option bit";
186          case PCRE_ERROR_BADMAGIC:     return "(pcre:) Bad magic number in code";
187          case PCRE_ERROR_UNKNOWN_NODE: return "(pcre:) Bad node in pattern";
188
189          /* Can't happen / not passed: */
190          case PCRE_ERROR_NOSUBSTRING:  return "(pcre:) Fire in power supply"; 
191          case PCRE_ERROR_NOMATCH:      return "(pcre:) Water in power supply";
192
193          /* PCRS errors: */
194          case PCRS_ERR_NOMEM:          return "(pcrs:) No memory";
195          case PCRS_ERR_CMDSYNTAX:      return "(pcrs:) Syntax error while parsing command";
196          case PCRS_ERR_STUDY:          return "(pcrs:) PCRE error while studying the pattern";
197          case PCRS_ERR_BADJOB:         return "(pcrs:) Bad job - NULL job, pattern or substitute";
198          case PCRS_WARN_BADREF:        return "(pcrs:) Backreference out of range";
199
200          /* What's that? */
201          default:  return "Unknown error";
202       }
203    }
204    /* error >= 0: No error */
205    return "(pcrs:) Everything's just fine. Thanks for asking.";
206
207 }
208
209
210 /*********************************************************************
211  *
212  * Function    :  pcrs_parse_perl_options
213  *
214  * Description :  This function parses a string containing the options to
215  *                Perl's s/// operator. It returns an integer that is the
216  *                pcre equivalent of the symbolic optstring.
217  *                Since pcre doesn't know about Perl's 'g' (global) or pcrs',
218  *                'T' (trivial) options but pcrs needs them, the corresponding
219  *                flags are set if 'g'or 'T' is encountered.
220  *                Note: The 'T' and 'U' options do not conform to Perl.
221  *             
222  * Parameters  :
223  *          1  :  optstring = string with options in perl syntax
224  *          2  :  flags = see description
225  *
226  * Returns     :  option integer suitable for pcre 
227  *
228  *********************************************************************/
229 static int pcrs_parse_perl_options(const char *optstring, int *flags)
230 {
231    size_t i;
232    int rc = 0;
233    *flags = 0;
234
235    if (NULL == optstring) return 0;
236
237    for (i = 0; i < strlen(optstring); i++)
238    {
239       switch(optstring[i])
240       {
241          case 'e': break; /* ToDo ;-) */
242          case 'g': *flags |= PCRS_GLOBAL; break;
243          case 'i': rc |= PCRE_CASELESS; break;
244          case 'm': rc |= PCRE_MULTILINE; break;
245          case 'o': break;
246          case 's': rc |= PCRE_DOTALL; break;
247          case 'x': rc |= PCRE_EXTENDED; break;
248          case 'U': rc |= PCRE_UNGREEDY; break;
249          case 'T': *flags |= PCRS_TRIVIAL; break;
250          default: break;
251       }
252    }
253    return rc;
254
255 }
256
257
258 /*********************************************************************
259  *
260  * Function    :  pcrs_compile_replacement
261  *
262  * Description :  This function takes a Perl-style replacement (2nd argument
263  *                to the s/// operator and returns a compiled pcrs_substitute,
264  *                or NULL if memory allocation for the substitute structure
265  *                fails.
266  *
267  * Parameters  :
268  *          1  :  replacement = replacement part of s/// operator
269  *                              in perl syntax
270  *          2  :  trivialflag = Flag that causes backreferences to be
271  *                              ignored.
272  *          3  :  capturecount = Number of capturing subpatterns in
273  *                               the pattern. Needed for $+ handling.
274  *          4  :  errptr = pointer to an integer in which error
275  *                         conditions can be returned.
276  *
277  * Returns     :  pcrs_substitute data structure, or NULL if an
278  *                error is encountered. In that case, *errptr has
279  *                the reason.
280  *
281  *********************************************************************/
282 static pcrs_substitute *pcrs_compile_replacement(const char *replacement, int trivialflag, int capturecount, int *errptr)
283 {
284    int i, k, l, quoted;
285    size_t length;
286    char *text;
287    pcrs_substitute *r;
288
289    i = k = l = quoted = 0;
290
291    /*
292     * Sanity check
293     */
294    if (NULL == replacement)
295    {
296       replacement = "";
297    }
298
299    /*
300     * Get memory or fail
301     */
302    if (NULL == (r = (pcrs_substitute *)malloc(sizeof(pcrs_substitute))))
303    {
304       *errptr = PCRS_ERR_NOMEM;
305       return NULL;
306    }
307    memset(r, '\0', sizeof(pcrs_substitute));
308
309    length = strlen(replacement);
310
311    if (NULL == (text = (char *)malloc(length + 1)))
312    {
313       free(r);
314       *errptr = PCRS_ERR_NOMEM;
315       return NULL;
316    }
317    memset(text, '\0', length + 1);
318    
319
320    /*
321     * In trivial mode, just copy the substitute text
322     */
323    if (trivialflag)
324    {
325       text = strncpy(text, replacement, length + 1);
326       k = length;
327    }
328
329    /*
330     * Else, parse, cut out and record all backreferences
331     */
332    else
333    {
334       while (i < (int)length)
335       {
336          /* Quoting */
337          if (replacement[i] == '\\')
338          {
339             if (quoted)
340             {
341                text[k++] = replacement[i++];
342                quoted = 0;
343             }
344             else
345             {
346                if (replacement[i+1] && strchr("tnrfae0", replacement[i+1]))
347                {
348                   switch (replacement[++i])
349                   {
350                   case 't':
351                      text[k++] = '\t';
352                      break;
353                   case 'n':
354                      text[k++] = '\n';
355                      break;
356                   case 'r':
357                      text[k++] = '\r';
358                      break;
359                   case 'f':
360                      text[k++] = '\f';
361                      break;
362                   case 'a':
363                      text[k++] = 7;
364                      break;
365                   case 'e':
366                      text[k++] = 27;
367                      break;
368                   case '0':
369                      text[k++] = '\0';
370                      break;
371                   }
372                   i++;
373                }
374                else
375                {
376                   quoted = 1;
377                   i++;
378                }
379             }
380             continue;
381          }
382
383          /* Backreferences */
384          if (replacement[i] == '$' && !quoted && i < (int)(length - 1))
385          {
386             char *symbol, symbols[] = "'`+&";
387             r->block_length[l] = k - r->block_offset[l];
388
389             /* Numerical backreferences */
390             if (isdigit((int)replacement[i + 1]))
391             {
392                while (i < (int)length && isdigit((int)replacement[++i]))
393                {
394                   r->backref[l] = r->backref[l] * 10 + replacement[i] - 48;
395                }
396                if (r->backref[l] > capturecount)
397                {
398                   *errptr = PCRS_WARN_BADREF;
399                }
400             }
401
402             /* Symbolic backreferences: */
403             else if (NULL != (symbol = strchr(symbols, replacement[i + 1])))
404             {
405                
406                if (symbol - symbols == 2) /* $+ */
407                {
408                   r->backref[l] = capturecount;
409                }
410                else if (symbol - symbols == 3) /* $& */
411                {
412                   r->backref[l] = 0;
413                }
414                else /* $' or $` */
415                {
416                   r->backref[l] = PCRS_MAX_SUBMATCHES + 1 - (symbol - symbols);
417                }
418                i += 2;
419             }
420
421             /* Invalid backref -> plain '$' */
422             else
423             {
424                goto plainchar;
425             }
426
427             /* Valid and in range? -> record */
428             if (r->backref[l] < PCRS_MAX_SUBMATCHES + 2)
429             {
430                r->backref_count[r->backref[l]] += 1;
431                r->block_offset[++l] = k;
432             }
433             else
434             {
435                *errptr = PCRS_WARN_BADREF;
436             }   
437             continue;
438          }
439          
440 plainchar:
441          /* Plain chars are copied */
442          text[k++] = replacement[i++];
443          quoted = 0;
444       }
445    } /* -END- if (!trivialflag) */
446
447    /*
448     * Finish & return
449     */
450    r->text = text;
451    r->backrefs = l;
452    r->block_length[l] = k - r->block_offset[l];
453
454    return r;
455
456 }
457
458
459 /*********************************************************************
460  *
461  * Function    :  pcrs_free_job
462  *
463  * Description :  Frees the memory used by a pcrs_job struct and its
464  *                dependant structures.
465  *
466  * Parameters  :
467  *          1  :  job = pointer to the pcrs_job structure to be freed
468  *
469  * Returns     :  a pointer to the next job, if there was any, or
470  *                NULL otherwise. 
471  *
472  *********************************************************************/
473 pcrs_job *pcrs_free_job(pcrs_job *job)
474 {
475    pcrs_job *next;
476
477    if (job == NULL)
478    {
479       return NULL;
480    }
481    else
482    {
483       next = job->next;
484       if (job->pattern != NULL) free(job->pattern);
485       if (job->hints != NULL) free(job->hints);
486       if (job->substitute != NULL)
487       {
488          if (job->substitute->text != NULL) free(job->substitute->text);
489          free(job->substitute);
490       }
491       free(job);
492    }
493    return next;
494
495 }
496
497
498 /*********************************************************************
499  *
500  * Function    :  pcrs_free_joblist
501  *
502  * Description :  Iterates through a chained list of pcrs_job's and
503  *                frees them using pcrs_free_job.
504  *
505  * Parameters  :
506  *          1  :  joblist = pointer to the first pcrs_job structure to
507  *                be freed
508  *
509  * Returns     :  N/A
510  *
511  *********************************************************************/
512 void pcrs_free_joblist(pcrs_job *joblist)
513 {
514    while ( NULL != (joblist = pcrs_free_job(joblist)) ) {};
515
516    return;
517
518 }
519
520
521 /*********************************************************************
522  *
523  * Function    :  pcrs_compile_command
524  *
525  * Description :  Parses a string with a Perl-style s/// command, 
526  *                calls pcrs_compile, and returns a corresponding
527  *                pcrs_job, or NULL if parsing or compiling the job
528  *                fails.
529  *
530  * Parameters  :
531  *          1  :  command = string with perl-style s/// command
532  *          2  :  errptr = pointer to an integer in which error
533  *                         conditions can be returned.
534  *
535  * Returns     :  a corresponding pcrs_job data structure, or NULL
536  *                if an error was encountered. In that case, *errptr
537  *                has the reason.
538  *
539  *********************************************************************/
540 pcrs_job *pcrs_compile_command(const char *command, int *errptr)
541 {
542    int i, k, l, quoted = FALSE;
543    size_t limit;
544    char delimiter;
545    char *tokens[4];   
546    pcrs_job *newjob;
547    
548    i = k = l = 0;
549    
550    /*
551     * Tokenize the perl command
552     */
553    limit = strlen(command);
554    if (limit < 4)
555    {
556       *errptr = PCRS_ERR_CMDSYNTAX;
557       return NULL;
558    }
559    else
560    {
561       delimiter = command[1];
562    }
563
564    tokens[l] = (char *) malloc(limit + 1);
565
566    for (i = 0; i <= (int)limit; i++)
567    {
568       
569       if (command[i] == delimiter && !quoted)
570       {
571          if (l == 3)
572          {
573             l = -1;
574             break;
575          }
576          tokens[0][k++] = '\0';
577          tokens[++l] = tokens[0] + k;
578          continue;
579       }
580       
581       else if (command[i] == '\\' && !quoted)
582       {
583          quoted = TRUE;
584          if (command[i+1] == delimiter) continue;
585       }
586       else
587       {
588          quoted = FALSE;
589       }
590       tokens[0][k++] = command[i];
591    }
592
593    /*
594     * Syntax error ?
595     */
596    if (l != 3)
597    {
598       *errptr = PCRS_ERR_CMDSYNTAX;
599       free(tokens[0]);
600       return NULL;
601    }
602    
603    newjob = pcrs_compile(tokens[1], tokens[2], tokens[3], errptr);
604    free(tokens[0]);
605    return newjob;
606    
607 }
608
609
610 /*********************************************************************
611  *
612  * Function    :  pcrs_compile
613  *
614  * Description :  Takes the three arguments to a perl s/// command
615  *                and compiles a pcrs_job structure from them.
616  *
617  * Parameters  :
618  *          1  :  pattern = string with perl-style pattern
619  *          2  :  substitute = string with perl-style substitute
620  *          3  :  options = string with perl-style options
621  *          4  :  errptr = pointer to an integer in which error
622  *                         conditions can be returned.
623  *
624  * Returns     :  a corresponding pcrs_job data structure, or NULL
625  *                if an error was encountered. In that case, *errptr
626  *                has the reason.
627  *
628  *********************************************************************/
629 pcrs_job *pcrs_compile(const char *pattern, const char *substitute, const char *options, int *errptr)
630 {
631    pcrs_job *newjob;
632    int flags;
633    int capturecount;
634    const char *error;
635
636    *errptr = 0;
637
638    /* 
639     * Handle NULL arguments
640     */
641    if (pattern == NULL) pattern = "";
642    if (substitute == NULL) substitute = "";
643
644
645    /* 
646     * Get and init memory
647     */
648    if (NULL == (newjob = (pcrs_job *)malloc(sizeof(pcrs_job))))
649    {
650       *errptr = PCRS_ERR_NOMEM;
651       return NULL;
652    }
653    memset(newjob, '\0', sizeof(pcrs_job));
654
655
656    /*
657     * Evaluate the options
658     */
659    newjob->options = pcrs_parse_perl_options(options, &flags);
660    newjob->flags = flags;
661
662
663    /*
664     * Compile the pattern
665     */
666    newjob->pattern = pcre_compile(pattern, newjob->options, &error, errptr, NULL);
667    if (newjob->pattern == NULL)
668    {
669       pcrs_free_job(newjob);
670       return NULL;
671    }
672
673
674    /*
675     * Generate hints. This has little overhead, since the
676     * hints will be NULL for a boring pattern anyway.
677     */
678    newjob->hints = pcre_study(newjob->pattern, 0, &error);
679    if (error != NULL)
680    {
681       *errptr = PCRS_ERR_STUDY;
682       pcrs_free_job(newjob);
683       return NULL;
684    }
685  
686
687    /* 
688     * Determine the number of capturing subpatterns. 
689     * This is needed for handling $+ in the substitute.
690     */
691    if (0 > (*errptr = pcre_fullinfo(newjob->pattern, newjob->hints, PCRE_INFO_CAPTURECOUNT, &capturecount)))
692    {
693       pcrs_free_job(newjob);
694       return NULL;
695    }
696  
697
698    /*
699     * Compile the substitute
700     */
701    if (NULL == (newjob->substitute = pcrs_compile_replacement(substitute, newjob->flags & PCRS_TRIVIAL, capturecount, errptr)))
702    {
703       pcrs_free_job(newjob);
704       return NULL;
705    }
706  
707    return newjob;
708
709 }
710
711
712 /*********************************************************************
713  *
714  * Function    :  pcrs_execute_list
715  *
716  * Description :  This is a multiple job wrapper for pcrs_execute().
717  *                Apply the regular substitutions defined by the jobs in
718  *                the joblist to the subject.
719  *                The subject itself is left untouched, memory for the result
720  *                is malloc()ed and it is the caller's responsibility to free
721  *                the result when it's no longer needed. 
722  *
723  *                Note: For convenient string handling, a null byte is
724  *                      appended to the result. It does not count towards the
725  *                      result_length, though.
726  *
727  *
728  * Parameters  :
729  *          1  :  joblist = the chained list of pcrs_jobs to be executed
730  *          2  :  subject = the subject string
731  *          3  :  subject_length = the subject's length 
732  *          4  :  result = char** for returning  the result 
733  *          5  :  result_length = size_t* for returning the result's length
734  *
735  * Returns     :  On success, the number of substitutions that were made.
736  *                 May be > 1 if job->flags contained PCRS_GLOBAL
737  *                On failiure, the (negative) pcre error code describing the
738  *                 failiure, which may be translated to text using pcrs_strerror().
739  *
740  *********************************************************************/
741 int pcrs_execute_list(pcrs_job *joblist, char *subject, size_t subject_length, char **result, size_t *result_length)
742 {
743    pcrs_job *job;
744    char *old, *new;
745    int hits, total_hits;
746  
747    old = subject;
748    *result_length = subject_length;
749    hits = total_hits = 0;
750
751    for (job = joblist; job != NULL; job = job->next)
752    {
753       hits = pcrs_execute(job, old, *result_length, &new, result_length);
754
755       if (old != subject) free(old);
756
757       if (hits < 0)
758       {
759          return(hits);
760       }
761       else
762       {
763          total_hits += hits;
764          old = new;
765       }
766    }
767
768    *result = new;
769    return(total_hits);
770
771 }
772
773
774 /*********************************************************************
775  *
776  * Function    :  pcrs_execute
777  *
778  * Description :  Apply the regular substitution defined by the job to the
779  *                subject.
780  *                The subject itself is left untouched, memory for the result
781  *                is malloc()ed and it is the caller's responsibility to free
782  *                the result when it's no longer needed.
783  *
784  *                Note: For convenient string handling, a null byte is
785  *                      appended to the result. It does not count towards the
786  *                      result_length, though.
787  *
788  * Parameters  :
789  *          1  :  job = the pcrs_job to be executed
790  *          2  :  subject = the subject (== original) string
791  *          3  :  subject_length = the subject's length 
792  *          4  :  result = char** for returning  the result 
793  *          5  :  result_length = size_t* for returning the result's length
794  *
795  * Returns     :  On success, the number of substitutions that were made.
796  *                 May be > 1 if job->flags contained PCRS_GLOBAL
797  *                On failiure, the (negative) pcre error code describing the
798  *                 failiure, which may be translated to text using pcrs_strerror().
799  *
800  *********************************************************************/
801 int pcrs_execute(pcrs_job *job, char *subject, size_t subject_length, char **result, size_t *result_length)
802 {
803    int offsets[3 * PCRS_MAX_SUBMATCHES],
804        offset,
805        i, k,
806        matches_found,
807        submatches,
808        max_matches = PCRS_MAX_MATCH_INIT;
809    size_t newsize;
810    pcrs_match *matches, *dummy;
811    char *result_offset;
812
813    offset = i = k = 0;
814
815    /* 
816     * Sanity check & memory allocation
817     */
818    if (job == NULL || job->pattern == NULL || job->substitute == NULL)
819    {
820       *result = NULL;
821       return(PCRS_ERR_BADJOB);
822    }
823
824    if (NULL == (matches = (pcrs_match *)malloc(max_matches * sizeof(pcrs_match))))
825    {
826       *result = NULL;
827       return(PCRS_ERR_NOMEM);
828    }
829    memset(matches, '\0', max_matches * sizeof(pcrs_match));
830
831
832    /*
833     * Find the pattern and calculate the space
834     * requirements for the result
835     */
836    newsize = subject_length;
837
838    while ((submatches = pcre_exec(job->pattern, job->hints, subject, (int)subject_length, offset, 0, offsets, 3 * PCRS_MAX_SUBMATCHES)) > 0)
839    {
840       job->flags |= PCRS_SUCCESS;
841       matches[i].submatches = submatches;
842
843       for (k = 0; k < submatches; k++)
844       {
845          matches[i].submatch_offset[k] = offsets[2 * k];
846
847          /* Note: Non-found optional submatches have length -1-(-1)==0 */
848          matches[i].submatch_length[k] = offsets[2 * k + 1] - offsets[2 * k]; 
849
850          /* reserve mem for each submatch as often as it is ref'd */
851          newsize += matches[i].submatch_length[k] * job->substitute->backref_count[k];
852       }
853       /* plus replacement text size minus match text size */
854       newsize += strlen(job->substitute->text) - matches[i].submatch_length[0]; 
855
856       /* chunk before match */
857       matches[i].submatch_offset[PCRS_MAX_SUBMATCHES] = 0;
858       matches[i].submatch_length[PCRS_MAX_SUBMATCHES] = offsets[0];
859       newsize += offsets[0] * job->substitute->backref_count[PCRS_MAX_SUBMATCHES];
860
861       /* chunk after match */
862       matches[i].submatch_offset[PCRS_MAX_SUBMATCHES + 1] = offsets[1];
863       matches[i].submatch_length[PCRS_MAX_SUBMATCHES + 1] = subject_length - offsets[1] - 1;
864       newsize += (subject_length - offsets[1]) * job->substitute->backref_count[PCRS_MAX_SUBMATCHES + 1];
865
866       /* Storage for matches exhausted? -> Extend! */
867       if (++i >= max_matches)
868       {
869          max_matches = (int)(max_matches * PCRS_MAX_MATCH_GROW);
870          if (NULL == (dummy = (pcrs_match *)realloc(matches, max_matches * sizeof(pcrs_match))))
871          {
872             free(matches);
873             *result = NULL;
874             return(PCRS_ERR_NOMEM);
875          }
876          matches = dummy;
877       }
878
879       /* Non-global search or limit reached? */
880       if (!(job->flags & PCRS_GLOBAL)) break;
881
882       /* Don't loop on empty matches */
883       if (offsets[1] == offset)
884          if ((size_t)offset < subject_length)
885             offset++;
886          else
887             break;
888       /* Go find the next one */
889       else
890          offset = offsets[1];
891    }
892    /* Pass pcre error through if (bad) failiure */
893    if (submatches < PCRE_ERROR_NOMATCH)
894    {
895       free(matches);
896       return submatches;   
897    }
898    matches_found = i;
899
900
901    /* 
902     * Get memory for the result (must be freed by caller!)
903     * and append terminating null byte.
904     */
905    if ((*result = (char *)malloc(newsize + 1)) == NULL)
906    {
907       free(matches);
908       return PCRS_ERR_NOMEM;
909    }
910    else
911    {
912       (*result)[newsize] = '\0';
913    }
914
915
916    /* 
917     * Replace
918     */
919    offset = 0;
920    result_offset = *result;
921
922    for (i = 0; i < matches_found; i++)
923    {
924       /* copy the chunk preceding the match */
925       memcpy(result_offset, subject + offset, (size_t)matches[i].submatch_offset[0] - offset); 
926       result_offset += matches[i].submatch_offset[0] - offset;
927
928       /* For every segment of the substitute.. */
929       for (k = 0; k <= job->substitute->backrefs; k++)
930       {
931          /* ...copy its text.. */
932          memcpy(result_offset, job->substitute->text + job->substitute->block_offset[k], job->substitute->block_length[k]);
933          result_offset += job->substitute->block_length[k];
934
935          /* ..plus, if it's not the last chunk, i.e.: There *is* a backref.. */
936          if (k != job->substitute->backrefs
937              /* ..in legal range.. */
938              && job->substitute->backref[k] < PCRS_MAX_SUBMATCHES + 2
939              /* ..and referencing a real submatch.. */
940              && job->substitute->backref[k] < matches[i].submatches
941              /* ..that is nonempty.. */
942              && matches[i].submatch_length[job->substitute->backref[k]] > 0)
943          {
944             /* ..copy the submatch that is ref'd. */
945             memcpy(
946                result_offset,
947                subject + matches[i].submatch_offset[job->substitute->backref[k]],
948                matches[i].submatch_length[job->substitute->backref[k]]
949             );
950             result_offset += matches[i].submatch_length[job->substitute->backref[k]];
951          }
952       }
953       offset =  matches[i].submatch_offset[0] + matches[i].submatch_length[0];
954    }
955
956    /* Copy the rest. */
957    memcpy(result_offset, subject + offset, subject_length - offset);
958
959    *result_length = newsize;
960    free(matches);
961    return matches_found;
962
963 }
964
965
966 /*
967   Local Variables:
968   tab-width: 3
969   end:
970 */