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