- (Fix:) Copied CODE_STATUS #define from config.h.in
[privoxy.git] / pcrs.c
1 const char pcrs_rcs[] = "$Id: pcrs.c,v 1.7 2001/06/29 13:33:04 oes Exp $";
2
3 /*********************************************************************
4  *
5  * File        :  $Source: /cvsroot/ijbswa/current/pcrs.c,v $
6  *
7  * Purpose     :  This is the alpha release of libpcrs. It is only published
8  *                at this early stage of development, because it is
9  *                needed for a new feature in JunkBuster.
10  *
11  *                While no inconsistencies, memory leaks or functional bugs
12  *                are known at this time, there *could* be plenty ;-). Also,
13  *                Many pcre-specific options are not yet supported, and
14  *                error handling needs improvement.
15  *
16  *                pcrs is a supplement to the brilliant pcre library by Philip
17  *                Hazel (ph10@cam.ac.uk) and adds Perl-style substitution. That
18  *                is, it mimics Perl's 's' operator.
19  *
20  *                Currently, there's no documentation besides comments and the
21  *                source itself ;-)
22  *
23  *                Short note: I addition to perl's options, 'U' for ungreedy
24  *                and 't' for trivial (i.e.: ignore backrefs in the substitute)
25  *                are supported.
26  *
27  * Copyright   :  Written and Copyright (C) 2000, 2001 by Andreas S. Oesterhelt
28  *                <andreas@oesterhelt.org>
29  *
30  *                This program is free software; you can redistribute it 
31  *                and/or modify it under the terms of the GNU General
32  *                Public License as published by the Free Software
33  *                Foundation; either version 2 of the License, or (at
34  *                your option) any later version.
35  *
36  *                This program is distributed in the hope that it will
37  *                be useful, but WITHOUT ANY WARRANTY; without even the
38  *                implied warranty of MERCHANTABILITY or FITNESS FOR A
39  *                PARTICULAR PURPOSE.  See the GNU General Public
40  *                License for more details.
41  *
42  *                The GNU General Public License should be included with
43  *                this file.  If not, you can view it at
44  *                http://www.gnu.org/copyleft/gpl.html
45  *                or write to the Free Software Foundation, Inc., 59
46  *                Temple Place - Suite 330, Boston, MA  02111-1307, USA.
47  *
48  * Revisions   :
49  *    $Log: pcrs.c,v $
50  *    Revision 1.7  2001/06/29 13:33:04  oes
51  *    - Cleaned up, renamed and reordered functions,
52  *      improved comments
53  *    - Removed my_strsep
54  *    - Replaced globalflag with a general flags int
55  *      that holds PCRS_GLOBAL, PCRS_SUCCESS, and PCRS_TRIVIAL
56  *    - Introduced trivial option that will prevent pcrs
57  *      from honouring backreferences in the substitute,
58  *      which is useful for large substitutes that are
59  *      red in from somewhere and saves the pain of escaping
60  *      the backrefs
61  *    - Introduced convenience function pcrs_free_joblist()
62  *    - Split pcrs_make_job() into pcrs_compile(), which still
63  *      takes a complete s/// comand as argument and parses it,
64  *      and a new function pcrs_make_job, which takes the
65  *      three separate components. This should make for a
66  *      much friendlier frontend.
67  *    - Removed create_pcrs_job() which was useless
68  *    - Fixed a bug in pcrs_execute
69  *    - Success flag is now handled by pcrs instead of user
70  *    - Removed logentry from cancelled commit
71  *
72  *    Revision 1.6  2001/06/03 19:12:45  oes
73  *    added FIXME
74  *
75  *    Revision 1.5  2001/05/29 09:50:24  jongfoster
76  *    Unified blocklist/imagelist/permissionslist.
77  *    File format is still under discussion, but the internal changes
78  *    are (mostly) done.
79  *
80  *    Also modified interceptor behaviour:
81  *    - We now intercept all URLs beginning with one of the following
82  *      prefixes (and *only* these prefixes):
83  *        * http://i.j.b/
84  *        * http://ijbswa.sf.net/config/
85  *        * http://ijbswa.sourceforge.net/config/
86  *    - New interceptors "home page" - go to http://i.j.b/ to see it.
87  *    - Internal changes so that intercepted and fast redirect pages
88  *      are not replaced with an image.
89  *    - Interceptors now have the option to send a binary page direct
90  *      to the client. (i.e. ijb-send-banner uses this)
91  *    - Implemented show-url-info interceptor.  (Which is why I needed
92  *      the above interceptors changes - a typical URL is
93  *      "http://i.j.b/show-url-info?url=www.somesite.com/banner.gif".
94  *      The previous mechanism would not have intercepted that, and
95  *      if it had been intercepted then it then it would have replaced
96  *      it with an image.)
97  *
98  *    Revision 1.4  2001/05/25 14:12:40  oes
99  *    Fixed bug: Empty substitutes now detected
100  *
101  *    Revision 1.3  2001/05/25 11:03:55  oes
102  *    Added sanity check for NULL jobs to pcrs_exec_substitution
103  *
104  *    Revision 1.2  2001/05/22 18:46:04  oes
105  *
106  *    - Enabled filtering banners by size rather than URL
107  *      by adding patterns that replace all standard banner
108  *      sizes with the "Junkbuster" gif to the re_filterfile
109  *
110  *    - Enabled filtering WebBugs by providing a pattern
111  *      which kills all 1x1 images
112  *
113  *    - Added support for PCRE_UNGREEDY behaviour to pcrs,
114  *      which is selected by the (nonstandard and therefore
115  *      capital) letter 'U' in the option string.
116  *      It causes the quantifiers to be ungreedy by default.
117  *      Appending a ? turns back to greedy (!).
118  *
119  *    - Added a new interceptor ijb-send-banner, which
120  *      sends back the "Junkbuster" gif. Without imagelist or
121  *      MSIE detection support, or if tinygif = 1, or the
122  *      URL isn't recognized as an imageurl, a lame HTML
123  *      explanation is sent instead.
124  *
125  *    - Added new feature, which permits blocking remote
126  *      script redirects and firing back a local redirect
127  *      to the browser.
128  *      The feature is conditionally compiled, i.e. it
129  *      can be disabled with --disable-fast-redirects,
130  *      plus it must be activated by a "fast-redirects"
131  *      line in the config file, has its own log level
132  *      and of course wants to be displayed by show-proxy-args
133  *      Note: Boy, all the #ifdefs in 1001 locations and
134  *      all the fumbling with configure.in and acconfig.h
135  *      were *way* more work than the feature itself :-(
136  *
137  *    - Because a generic redirect template was needed for
138  *      this, tinygif = 3 now uses the same.
139  *
140  *    - Moved GIFs, and other static HTTP response templates
141  *      to project.h
142  *
143  *    - Some minor fixes
144  *
145  *    - Removed some >400 CRs again (Jon, you really worked
146  *      a lot! ;-)
147  *
148  *    Revision 1.1.1.1  2001/05/15 13:59:02  oes
149  *    Initial import of version 2.9.3 source tree
150  *
151  *
152  *********************************************************************/
153 \f
154
155 #include <pcre.h>
156 #include <string.h>
157 #include "pcrs.h"
158 const char pcrs_h_rcs[] = PCRS_H_VERSION;
159
160
161 /*********************************************************************
162  *
163  * Function    :  pcrs_compile_perl_options
164  *
165  * Description :  This function parses a string containing the options to
166  *                Perl's s/// operator. It returns an integer that is the
167  *                pcre equivalent of the symbolic optstring.
168  *                Since pcre doesn't know about Perl's 'g' (global) or pcrs',
169  *                'T' (trivial) options but pcrs needs them, the corresponding
170  *                flags are set if 'g'or 'T' is encountered.
171  *                Note: The 'T' and 'U' options do not conform to Perl.
172  *             
173  * Parameters  :
174  *          1  :  optstring = string with options in perl syntax
175  *          2  :  flags = see description
176  *
177  * Returns     :  option integer suitable for pcre 
178  *
179  *********************************************************************/
180 int pcrs_compile_perl_options(char *optstring, int *flags)
181 {
182    size_t i;
183    int rc = 0;
184    *flags = 0;
185    for (i=0; i < strlen(optstring); i++)
186    {
187       switch(optstring[i])
188       {
189          case 'e': break;
190          case 'g': *flags |= PCRS_GLOBAL; break;
191          case 'i': rc |= PCRE_CASELESS; break;
192          case 'm': rc |= PCRE_MULTILINE; break;
193          case 'o': break;
194          case 's': rc |= PCRE_DOTALL; break;
195          case 'x': rc |= PCRE_EXTENDED; break;
196          case 'U': rc |= PCRE_UNGREEDY; break;
197            case 'T': *flags |= PCRS_TRIVIAL; break;
198          default:  break;
199       }
200    }
201    return rc;
202
203 }
204
205
206 /*********************************************************************
207  *
208  * Function    :  pcrs_compile_replacement
209  *
210  * Description :  This function takes a Perl-style replacement (2nd argument
211  *                to the s/// operator and returns a compiled pcrs_substitute,
212  *                or NULL if memory allocation for the substitute structure
213  *                fails.
214  *
215  * Parameters  :
216  *          1  :  replacement = replacement part of s/// operator
217  *                              in perl syntax
218  *          2  :  errptr = pointer to an integer in which error
219  *                         conditions can be returned.
220  *
221  * Returns     :  pcrs_substitute data structure, or NULL if an
222  *                error is encountered. In that case, *errptr has
223  *                the reason.
224  *
225  *********************************************************************/
226 pcrs_substitute *pcrs_compile_replacement(char *replacement, int trivialflag, int *errptr)
227 {
228    int length, i, k = 0, l = 0, quoted = 0, idx;
229    char *text, *num_ptr, *numbers = "0123456789";
230    pcrs_substitute *r;
231
232    r = (pcrs_substitute *)malloc(sizeof(pcrs_substitute));
233    if (r == NULL) return NULL;
234    memset(r, '\0', sizeof(pcrs_substitute));
235
236    text = strdup(replacement);      /* must be free()d by caller */
237    if (text  == NULL)
238    {
239       *errptr = PCRS_ERR_NOMEM;
240       free(r);
241       return NULL;
242    }
243
244    length = strlen(replacement);
245
246    if (trivialflag)
247    {
248        k = length;
249    }
250    else
251    {
252       for (i=0; i < length; i++)
253       {
254          /* Backslash treatment */
255          if (replacement[i] == '\\')
256          {
257             if (quoted)
258             {
259                text[k++] = replacement[i];
260                quoted = 0;
261             }
262             else
263             {
264                quoted = 1;
265             }
266             continue;
267          }
268
269          /* Dollar treatment */
270          if (replacement[i] == '$' && !quoted && i < length - 1)
271          {
272             if (strchr("0123456789&", replacement[i + 1]) == NULL)
273             {
274                text[k++] = replacement[i];
275             }
276             else
277             {
278                r->block_length[l] = k - r->block_offset[l];
279                r->backref[l] = 0;
280                if (replacement[i + 1] != '&')
281                {
282                   while ((num_ptr = strchr(numbers, replacement[++i])) != NULL && i < length)
283                   {
284                      idx = num_ptr - numbers;
285                      r->backref[l] = r->backref[l] * 10 + idx;
286                   }
287                   i--;
288                }
289                else
290                   i++;
291                if (r->backref[l] < PCRS_MAX_SUBMATCHES)
292                   r->backref_count[r->backref[l]] += 1;
293                l++;
294                r->block_offset[l] = k;
295             }
296             continue;
297          }
298
299          /* Plain char treatment */
300          text[k++] = replacement[i];
301          quoted = 0;
302       }
303    } /* -END- if (!trivialflag) */
304
305    text[k] = '\0';
306    r->text = text;
307    r->backrefs = l;
308    r->block_length[l] = k - r->block_offset[l];
309    return r;
310
311 }
312
313
314 /*********************************************************************
315  *
316  * Function    :  pcrs_free_job
317  *
318  * Description :  Frees the memory used by a pcrs_job struct and its
319  *                dependant structures. Returns a pointer to the next
320  *                job, if there was any, or NULL otherwise.
321  *
322  * Parameters  :
323  *          1  :  job = pointer to the pcrs_job structure to be freed
324  *
325  * Returns     :  a pointer to the next job, if there was any, or
326  *                NULL otherwise. 
327  *
328  *********************************************************************/
329 pcrs_job *pcrs_free_job(pcrs_job *job)
330 {
331    pcrs_job *next;
332
333    if (job == NULL)
334    {
335       return NULL;
336    }
337    else
338    {
339       next = job->next;
340       if (job->pattern != NULL) free(job->pattern);
341       if (job->hints != NULL) free(job->hints);
342       if (job->substitute != NULL)
343       {
344          if (job->substitute->text != NULL) free(job->substitute->text);
345          free(job->substitute);
346       }
347       free(job);
348    }
349    return next;
350
351 }
352
353 /*********************************************************************
354  *
355  * Function    :  pcrs_free_joblist
356  *
357  * Description :  Iterates through a chained list of pcrs_job's and
358  *                frees them using pcrs_free_job.
359  *
360  * Parameters  :
361  *          1  :  joblist = pointer to the first pcrs_job structure to
362  *                be freed
363  *
364  * Returns     :  N/A
365  *
366  *********************************************************************/
367 void pcrs_free_joblist(pcrs_job *joblist)
368 {
369    while ( NULL != (joblist = pcrs_free_job(joblist)) ) {};
370
371    return;
372
373 }
374
375
376 /*********************************************************************
377  *
378  * Function    :  pcrs_compile
379  *
380  * Description :  Main entry point. Takes a string with a Perl-style
381  *                s/// command and returns a corresponding pcrs_job,
382  *                or NULL if compiling the job fails at any stage.
383  *
384  * Parameters  :
385  *          1  :  command = string with perl-style s/// command
386  *          2  :  errptr = pointer to an integer in which error
387  *                         conditions can be returned.
388  *
389  * Returns     :  a corresponding pcrs_job data structure, or NULL
390  *                if an error was encountered. In that case, *errptr
391  *                has the reason.
392  *
393  *********************************************************************/
394 pcrs_job *pcrs_compile(char *command, int *errptr)
395 {
396    int i, k, l, limit, quoted = FALSE;
397    char delimiter;
398    char *tokens[4];   
399    pcrs_job *newjob;
400
401    i = k = l = 0;
402
403    /*
404     * Tokenize the perl command
405     */
406    limit = strlen(command);
407    if (limit < 4)
408    {
409       *errptr = PCRS_ERR_CMDSYNTAX;
410       return NULL;
411    }
412    else
413    {
414      delimiter = command[1];
415    }
416
417    tokens[l] = (char *) malloc(limit + 1);
418
419    for (i=0; i <= limit; i++)
420    {
421
422       if (command[i] == delimiter && !quoted)
423       {
424            if (l == 3)
425                 {
426                    l = -1;
427             break;
428          }
429           tokens[0][k++] = '\0';
430          tokens[++l] = tokens[0] + k;
431          continue;
432       }
433
434       else if (command[i] == '\\' && !quoted && i+1 < limit && command[i+1] == delimiter)
435       {
436          quoted = TRUE;
437          continue;
438       }
439       tokens[0][k++] = command[i];
440       quoted = FALSE;
441    }
442
443
444    /*
445     * Syntax error ?
446     */
447    if (l != 3)
448    {
449       *errptr = PCRS_ERR_CMDSYNTAX;
450       free(tokens[0]);
451       return NULL;
452    }
453
454    newjob = pcrs_make_job(tokens[1], tokens[2], tokens[3], errptr);
455    free(tokens[0]);
456    return newjob;
457
458 }
459
460
461 /*********************************************************************
462  *
463  * Function    :  pcrs_make_job
464  *
465  * Description :  Takes the three arguments to a perl s/// command
466  *                and compiles a pcrs_job structure from them.
467  *
468  * Parameters  :
469  *          1  :  pattern = string with perl-style pattern
470  *          2  :  substitute = string with perl-style substitute
471  *          3  :  options = string with perl-style options
472  *          4  :  errptr = pointer to an integer in which error
473  *                         conditions can be returned.
474  *
475  * Returns     :  a corresponding pcrs_job data structure, or NULL
476  *                if an error was encountered. In that case, *errptr
477  *                has the reason.
478  *
479  *********************************************************************/
480 pcrs_job *pcrs_make_job(char *pattern, char *substitute, char *options, int *errptr)
481 {
482    pcrs_job *newjob;
483    int flags;
484    const char *error;
485
486    /* 
487     * Handle NULL arguments
488     */
489    if (pattern == NULL) pattern = "";
490    if (substitute == NULL) substitute = "";
491    if (options == NULL) options = "";
492
493    /* 
494     * Get and init memory
495     */
496    if (NULL == (newjob = (pcrs_job *)malloc(sizeof(pcrs_job))))
497    {
498       *errptr = PCRS_ERR_NOMEM;
499       return NULL;
500    }
501    memset(newjob, '\0', sizeof(pcrs_job));
502
503
504    /*
505     * Evaluate the options
506     */
507    newjob->options = pcrs_compile_perl_options(options, &flags);
508    newjob->flags = flags;
509
510
511    /*
512     * Compile the pattern
513     */
514    newjob->pattern = pcre_compile(pattern, newjob->options, &error, errptr, NULL);
515    if (newjob->pattern == NULL)
516    {
517       pcrs_free_job(newjob);
518       return NULL;
519    }
520
521
522    /*
523     * Generate hints. This has little overhead, since the
524     * hints will be NULL for a boring pattern anyway.
525     */
526    newjob->hints = pcre_study(newjob->pattern, 0, &error);
527    if (error != NULL)
528    {
529       *errptr = PCRS_ERR_STUDY;
530       pcrs_free_job(newjob);
531       return NULL;
532    }
533  
534
535    /*
536     * Compile the substitute
537     */
538    if (NULL == (newjob->substitute = pcrs_compile_replacement(substitute, newjob->flags & PCRS_TRIVIAL, errptr)))
539    {
540       pcrs_free_job(newjob);
541       return NULL;
542    }
543  
544    return newjob;
545
546 }
547
548
549 /*********************************************************************
550  *
551  * Function    :  pcrs_execute
552  *
553  * Description :  Modify the subject by executing the regular substitution
554  *                defined by the job. Since the result may be longer than
555  *                the subject, its space requirements are precalculated in
556  *                the matching phase and new memory is allocated accordingly.
557  *                It is the caller's responsibility to free the result when
558  *                it's no longer needed.
559  *
560  * Parameters  :
561  *          1  :  job = the pcrs_job to be executed
562  *          2  :  subject = the subject (== original) string
563  *          3  :  subject_length = the subject's length 
564  *                INCLUDING the terminating zero, if string!
565  *          4  :  result = char** for returning  the result 
566  *          5  :  result_length = int* for returning the result's length
567  *
568  * Returns     :  the number of substitutions that were made. May be > 1
569  *                if job->flags contained PCRS_GLOBAL
570  *
571  *********************************************************************/
572 int pcrs_execute(pcrs_job *job, char *subject, int subject_length, char **result, int *result_length)
573 {
574    int offsets[3 * PCRS_MAX_SUBMATCHES],
575        offset, i, k,
576        matches_found,
577        newsize,
578        submatches;
579    pcrs_match matches[PCRS_MAX_MATCHES];
580    char *result_offset;
581
582    offset = i = k = 0;
583
584    /* 
585     * Sanity check
586     */
587    if (job == NULL || job->pattern == NULL || job->substitute == NULL)
588    {
589       *result = NULL;
590       return(PCRS_ERR_BADJOB);
591    }
592
593    
594    /*
595     * Find the pattern and calculate the space
596     * requirements for the result (newsize)
597     */
598    newsize=subject_length;
599
600    while ((submatches = pcre_exec(job->pattern, job->hints, subject, subject_length, offset, 0, offsets, 3 * PCRS_MAX_SUBMATCHES)) > 0)
601    {
602       job->flags |= PCRS_SUCCESS;
603       matches[i].submatches = submatches;
604       for (k=0; k < submatches; k++)
605       {
606          matches[i].submatch_offset[k] = offsets[2 * k];
607
608          /* Note: Non-found optional submatches have length -1-(-1)==0 */
609          matches[i].submatch_length[k] = offsets[2 * k + 1] - offsets[2 * k]; 
610
611          /* reserve mem for each submatch as often as it is ref'd */
612          newsize += matches[i].submatch_length[k] * job->substitute->backref_count[k]; 
613       }
614       /* plus replacement text size minus match text size */
615       newsize += strlen(job->substitute->text) - matches[i].submatch_length[0]; 
616
617       /* Non-global search or limit reached? */
618       if (++i >= PCRS_MAX_MATCHES || !(job->flags & PCRS_GLOBAL) ) break;
619
620       /* Don't loop on empty matches */
621       if (offsets[1] == offset)
622          if (offset < subject_length)
623             offset++;
624          else
625             break;
626       /* Go find the next one */
627       else
628          offset = offsets[1];
629    }
630    /* Pass pcre error through if failiure*/
631    if (submatches < -1) return submatches;   
632    matches_found = i;
633
634
635    /* 
636     * Get memory for the result
637     */
638    if ((*result = (char *)malloc(newsize)) == NULL)   /* must be free()d by caller */
639    {
640       return PCRS_ERR_NOMEM;
641    }
642
643
644    /* 
645     * Replace
646     */
647    offset = 0;
648    result_offset = *result;
649
650    for (i=0; i < matches_found; i++)
651    {
652       /* copy the chunk preceding the match */
653       memcpy(result_offset, subject + offset, matches[i].submatch_offset[0] - offset); 
654       result_offset += matches[i].submatch_offset[0] - offset;
655
656       /* For every segment of the substitute.. */
657       for (k=0; k <= job->substitute->backrefs; k++)
658       {
659          /* ...copy its text.. */
660          memcpy(result_offset, job->substitute->text + job->substitute->block_offset[k], job->substitute->block_length[k]);
661          result_offset += job->substitute->block_length[k];
662
663          /* ..plus, if it's not the last chunk (i.e.: There IS a backref).. */
664          if (k != job->substitute->backrefs
665              /* ..and a nonempty match.. */
666              && matches[i].submatch_length[job->substitute->backref[k]] > 0
667              /* ..and in legal range, ... */
668              && job->substitute->backref[k] <= PCRS_MAX_SUBMATCHES)
669          {
670             /* copy the submatch that is ref'd. */
671             memcpy(
672                result_offset,
673                subject + matches[i].submatch_offset[job->substitute->backref[k]],
674                matches[i].submatch_length[job->substitute->backref[k]]
675             );
676             result_offset += matches[i].submatch_length[job->substitute->backref[k]];
677          }
678       }
679       offset =  matches[i].submatch_offset[0] + matches[i].submatch_length[0];
680    }
681
682    /* Copy the rest. */
683    memcpy(result_offset, subject + offset, subject_length - offset);
684
685    *result_length = newsize;
686    return matches_found;
687
688 }
689
690
691 /*
692   Local Variables:
693   tab-width: 3
694   end:
695 */