home *** CD-ROM | disk | FTP | other *** search
/ DP Tool Club 31 / CDASC_31_1996_juillet_aout.iso / internet / rnr214.zip / RNRCHAR.PAS < prev    next >
Pascal/Delphi Source File  |  1996-01-30  |  15KB  |  550 lines

  1. unit rnrchar;
  2.  
  3.   Character set support for rnr - handles conversion between
  4.   IBM PC codepages and character sets used on USENET and mail.
  5.  
  6.   The implementation follows the MIME standard, defined in RFC 1521.
  7.   However, it does not implement all of the required transfer-encodings
  8.   and document types; thus it cannot claim to be MIME compatible.
  9.  
  10.   In order for the conversion to be put into effect, the option
  11.   '--local-charset filename' must be used.
  12.  
  13.   By Henrik Storner (storner@osiris.ping.dk)
  14.   modified by Russell_Schulz@locutus.ofB.ORG
  15. }
  16.  
  17. {$i rnr-def.pas}
  18.  
  19. interface
  20.  
  21. uses rnrglob, genericf, rnrio, rnrproc, rnrfile;
  22.  
  23. { These need to be global, as they are written in headers }
  24.  
  25. var 
  26.   postingsetname: string;    { charset used for outgoing posts }
  27.   mailingsetname: string;    { charset used for outgoing mail  }
  28.   mailxfername  : string;    { transfer-encoding for mail      }
  29.  
  30.  
  31. procedure linetolocal(var s: string);
  32. procedure localtoline(var s: string);
  33. procedure setreadencoding(chsetheader: string; encheader: string);
  34. procedure setsendencoding(chsetheader: string; encheader: string);
  35. procedure loadcharsets(fn: string);
  36.  
  37.  
  38. implementation
  39.  
  40. type
  41.   charset_ptr = ^charset_rec;
  42.   charset_rec = record
  43.                   name: string;
  44.                   cnvtable : string;
  45.                   next : charset_ptr;
  46.                 end;
  47.  
  48. { RFC 1521 transfer encodings }
  49.  
  50.   xferenc = (
  51.     xfer_7bit,    {* 7 bit - no translation needed  *}
  52.     xfer_8bit,    {* 8 bit - no translation needed  *}
  53.     xfer_binary,  {* binary - no translation needed *}
  54.     xfer_quoted,  {* Quoted printable               *}
  55.     xfer_base64,  {* Base64                         *}
  56.     xfer_user     {* Userdefined - don't touch!     *}
  57.     );
  58.  
  59. var
  60.  
  61. { These are only used locally }
  62.  
  63.   charsets      : charset_ptr;
  64.  
  65. { For decoding news and mail; default translation if no MIME header }
  66.  
  67.   defaultset    : charset_ptr;
  68.  
  69. { For decoding news and mail; set up for each article }
  70.  
  71.   decodingset   : string;  { Translation table for line->local         }
  72.   decodingxfer  : xferenc; { Current transfer encoding                 }
  73.  
  74. { For encoding news and mail; set up after message is edited         }
  75. { NOTE: This has to be setup after message is edited, as user may    }
  76. { choose to alter the charset/transfer-encoding headers.             }
  77.  
  78.   encodingset   : string;  { Translation table for local->line         }
  79.   encodingxfer  : xferenc; { Transfer-encoding                         }
  80.  
  81. { Characters passed uncoded when using quoted-printable }
  82.  
  83.   mailsafechars : set of char;
  84.  
  85.  
  86.  
  87. function hexdigitval(c: char): integer;
  88.  
  89. begin
  90.   case c of
  91.   '0'..'9': hexdigitval:= ord(c) - ord('0');
  92.   'A'..'F': hexdigitval:= ord(c) - ord('A') + 10;
  93.   'a'..'f': hexdigitval:= ord(c) - ord('a') + 10;
  94.   else      hexdigitval:= -1;
  95.   end;
  96. end;
  97.  
  98. function hexstring(v: byte): string;
  99.  
  100. const
  101.   hx : array[0..15] of char = '0123456789ABCDEF';
  102. begin
  103.   hexstring:= hx[v div 16] + hx[v mod 16];
  104. end;
  105.  
  106.  
  107. procedure transferencode(var s:string; encoding: xferenc);
  108.  
  109. { Convert a line of data to the selected transfer-encoding }
  110.  
  111. var
  112.   r: string;
  113.   i: integer;
  114.   lastbreak: integer;
  115.  
  116. begin
  117.   case (encoding) of
  118.    xfer_7bit,
  119.    xfer_8bit,
  120.    xfer_binary,
  121.    xfer_user:
  122.      { Don't change it }
  123.      ;
  124.  
  125.    xfer_base64:
  126.      { NOT SUPPORTED }
  127.      ;
  128.  
  129.    xfer_quoted:
  130.      begin
  131.      r := '';
  132.      lastbreak:= 0; { Indicates last position for soft line-break }
  133.  
  134.      for i:= 1 to length(s) do
  135.        if ( (s[i] in mailsafechars) or
  136.             ((s[i] in [tab, ' ']) and (i < length(s))) ) then
  137.          begin
  138.            { These are not encoded: Plain ascii except '=', plus }
  139.            { SPACE and TAB that are not last on the line.        }
  140.            if ((length(r) - lastbreak) > 73) then
  141.              begin
  142.                { Need to insert soft linebreak }
  143.                r := r + '=' + lf;
  144.                lastbreak:= length(r);
  145.              end;
  146.            r := r + s[i];
  147.          end
  148.        else
  149.          begin
  150.            { These get encoded }
  151.            if ((length(r) - lastbreak) > 71) then
  152.              begin
  153.                r := r + '=' + lf;
  154.                lastbreak:= length(r);
  155.              end;
  156.            r := r + '=' + hexstring(ord(s[i]));
  157.          end;
  158.  
  159.      s := r;
  160.      end; { xfer_quoted }
  161.   end;
  162. end;
  163.  
  164.  
  165. procedure transferdecode(var s:string; encoding: xferenc);
  166.  
  167. { Convert a line of data from the transferencoding form to }
  168. { canonical form. This must be done BEFORE applying the    }
  169. { conversions necessary for the charset used.              }
  170.  
  171. var 
  172.   r: string;
  173.   i: integer;
  174.   hexval: integer;
  175.  
  176. begin
  177.   case (encoding) of
  178.     xfer_7bit,   { 7-bit is OK.                                            }
  179.     xfer_8bit,   { 8-bit is OK.                                            }
  180.     xfer_binary, { Binary may not work, because of linelength limitations! }
  181.     xfer_user:   { No guarantee that this will work.                       }
  182.      { Don't do a thing here }
  183.      ;
  184.  
  185.     xfer_base64: 
  186.        if s<>'' then
  187.          s:= '<base64 decoding not supported>';
  188.  
  189.     xfer_quoted:
  190.  
  191. { Quoted-printable encoding. }
  192.  
  193. { This encoding passes most ASCII characters unaltered.  Exception is     }
  194. { control characters and 8-bit characters, and the '=', which are encoded }
  195. { as <char> := "="hexval So decode by scanning for '=' and rebuilding     }
  196. { original This encoding may add linebreaks in the middle of an input     }
  197. { line, or may specify that no linebreak should occur at the end of an    }
  198. { inputline. We are unable to obey neither of these.                      }
  199.  
  200.        begin
  201.          r := '';
  202.          i := 1;
  203.          while (i <= length(s)) do
  204.            begin
  205.            if (s[i] = '=') then
  206.              begin
  207.                inc(i);
  208.                if (i < length(s)) then
  209.                  begin
  210.                    { Expect 2-digit hex code here.             }
  211.                    { i<length(s) ==> we do have two characters }
  212.                    hexval:= 16*hexdigitval(s[i]) + hexdigitval(s[i+1]);
  213.                    if (hexval >= 0) then
  214.                      r := r + chr(hexval)
  215.                    else
  216.                      { Coding error! Not valid hex digit. }
  217.                      { Pass thru unaltered. Warn user ??  }
  218.                      r := r + copy(s, i-1, 3);
  219.  
  220.                    inc(i,2);
  221.                  end
  222.                else  { i>=length(s) }
  223.                  if (i > length(s)) then
  224.                    begin
  225.                      { '=' as last character ==> soft line break }
  226.                      { Cannot handle this.                       }
  227.                    end
  228.                  else
  229.                    begin
  230.                      { Coding error - only one hex digit. }
  231.                      { Pass thru unaltered. Warn user ??  }
  232.                      r := r + '=' + s[i];
  233.                      inc(i);
  234.                    end;
  235.                end
  236.             else { not `=' }
  237.               begin
  238.                 { Ordinary, unquoted character. Could check that  }
  239.                 { it is a legal character (ASCII), but why bother }
  240.                 r := r + s[i];
  241.                 inc(i);
  242.               end;
  243.           end;
  244.  
  245.           s := r;
  246.        end; { quoted printable }
  247.  
  248.   end; { case }
  249. end;
  250.  
  251.  
  252.  
  253. procedure detectcharset(var chsetheader: string; encheader: string;
  254.                         var xlate: string; var xfer: xferenc;
  255.                         defset: charset_ptr);
  256. var 
  257.   p: charset_ptr;
  258.   found: boolean;
  259.  
  260. begin
  261.  
  262. { Process the character set header (if any) and set up translation    }
  263. { tables for the linetolocal routine. Takes the contents of the       }
  264. { 'Content-type' and 'Content-transfer-encoding' header as parameter. }
  265.  
  266. { First, detect the character set defined by the content-type }
  267.  
  268.   p := charsets;
  269.   found:= false;
  270.   chsetheader := lower(chsetheader);
  271.  
  272.   if (chsetheader <> '') then
  273.     while ((p <> nil) and (not found)) do
  274.       begin
  275.         found := (pos(p^.name, chsetheader) <> 0);
  276.         if (not found) then
  277.           p:= p^.next;
  278.       end;  
  279.  
  280.   if found then
  281.     xlate:= p^.cnvtable
  282.   else
  283.     begin
  284.       if (defset = nil) then
  285.         xlate := ''
  286.       else
  287.         xlate:= defset^.cnvtable;
  288.     end;
  289.  
  290.  
  291. { Next, determine the transfer-encoding }
  292.  
  293.   encheader := lower(encheader);
  294.   if (pos('7bit', encheader) <> 0) then
  295.     xfer := xfer_7bit
  296.   else if (pos('8bit', encheader) <> 0) then
  297.     xfer := xfer_8bit
  298.   else if (pos('quoted-printable', encheader) <> 0) then
  299.     xfer := xfer_quoted
  300.   else if (pos('base64', encheader) <> 0) then
  301.     xfer := xfer_base64
  302.   else if (pos('binary', encheader) <> 0) then
  303.     xfer := xfer_binary
  304.   else if (encheader <> '') then
  305.     xfer := xfer_user
  306.   else
  307.     xfer := xfer_7bit;  { Default if no transfer-encoding specified }
  308.  
  309. end;
  310.  
  311.  
  312. procedure setreadencoding(chsetheader: string; encheader: string);
  313.  
  314. begin
  315.   detectcharset(chsetheader, encheader, decodingset, decodingxfer, defaultset);
  316. end;
  317.  
  318.  
  319. procedure setsendencoding(chsetheader: string; encheader: string);
  320.  
  321. var
  322.   i: integer;
  323.   reverseset: string;
  324.  
  325. begin
  326.   detectcharset(chsetheader, encheader, reverseset, encodingxfer, nil);
  327.  
  328. { Have to reverse the translation, as this is for local->line }
  329.  
  330.   encodingset:= '';
  331.   for i:= 1 to 255 do
  332.     encodingset:= encodingset + chr(i);
  333.  
  334. { If no header in message, then reverseset is empty }
  335.  
  336.   if (reverseset <> '') then  
  337.     for i:= 1 to 255 do
  338.       encodingset[ord(reverseset[i])] := chr(i);
  339. end;
  340.  
  341.  
  342.  
  343. procedure loadcharsets;
  344.  
  345. type
  346.   wanted_type = (name_wanted, trans_wanted);
  347.  
  348. var
  349.   f: text;
  350.   l: string;
  351.   newset: charset_ptr;
  352.   p: charset_ptr;
  353.   w: wanted_type;
  354.   token: string;
  355.   i, err, v1, v2: integer;
  356.  
  357. begin
  358.   w := name_wanted;
  359.  
  360.   safereset(f,fn);
  361.   if (fileresult > 0) then
  362.     begin
  363.       xwritelnss('Cannot open characterset file ', fn);
  364.       shutdown(1);
  365.     end;
  366.  
  367.   while not eof(f) do
  368.     begin
  369.       readln(f, l);
  370.       token:= chopfirstw(l);
  371.       if (length(token) > 0) then
  372.         begin
  373.         if (token[1] <> '#') then
  374.           begin
  375.             case w of
  376.              name_wanted: 
  377.               begin
  378.                 new(newset);
  379.                 newset^.name:= token;
  380.                 newset^.cnvtable:= '';
  381.                 for i:= 1 to 255 do
  382.                   newset^.cnvtable:= newset^.cnvtable + chr(i);
  383.                 newset^.next:= charsets;
  384.                 charsets:= newset;
  385.                 w := trans_wanted;
  386.  
  387.                 notquietlnss('Charset loaded: ', newset^.name);
  388.  
  389.                 repeat
  390.                   token := lower(chopfirstw(l));
  391.  
  392.                   if (token = '/post') then
  393.                     begin
  394.                       postingsetname:= newset^.name;
  395.                       notquietlns(' - used for outgoing posts');
  396.                     end
  397.                   else if (copy(token, 1, 5) = '/mail') then
  398.                     begin
  399.                       mailingsetname := newset^.name;
  400.                       notquietlns(' - used for outgoing mail');
  401.  
  402.                       token:= copy(token, 7, 255);
  403.                       if (token = '7bit') then
  404.                         mailxfername := '7bit'
  405.                       else if (token = '8bit') then
  406.                         mailxfername := '8bit'
  407.                       else if (token = 'quoted') then
  408.                         mailxfername := 'quoted-printable'
  409.                       else if (token = 'quoted-safe') then
  410.                         begin
  411.  
  412.                            { Extra safe quoted-printable }
  413.                            { Will survive EBCDIC gateways unscathed }
  414.                            mailxfername := 'quoted-printable';
  415.                            mailsafechars := ['A'..'Z', 'a'..'z', '0'..'9',
  416.                                           #39..#41, #43..#47, #58, #63];
  417.  
  418.                            notquietlns(
  419.               ' - (enabling EBCDIC safe quoted-printable encoding)');
  420.  
  421.                         end
  422.                       else 
  423.                         begin
  424.                           xwritelnss('unknown mailset transferencoding: ',
  425.                             token);
  426.                           shutdown(1);
  427.                         end; 
  428.  
  429.                       notquietlnss(' - mail transfer-encoding: ', mailxfername);
  430.                     end
  431.                   else if (token = '/default') then
  432.                     begin
  433.                       defaultset := newset;
  434.                       notquietlns(' - used as default for reading posts');
  435.                     end
  436.                   else if (token <> '') then
  437.                     begin
  438.                       xwritelnss('unknown charset option:', token);
  439.                       shutdown(1);
  440.                     end;
  441.                 until (token = '');
  442.               end;
  443.  
  444.              trans_wanted:
  445.                begin
  446.                if (lower(token) = 'end') then
  447.                  w := name_wanted
  448.                else
  449.                  begin
  450.                    i:= pos('=', token);
  451.                    if ((i = 0) or (i = length(token))) then
  452.                      begin
  453.                        xwritelnss('Bad charset conversion', token);
  454.                        shutdown(1);
  455.                      end;
  456.  
  457.                    val(copy(token, 1, i-1), v1, err);
  458.                    if (err = 0) then
  459.                      val(copy(token, i+1, length(token)-i), v2, err);
  460.                    if (err <> 0) then
  461.                      begin
  462.                        xwritelnss('Bad charset conversion: ', token);
  463.                        shutdown(1);
  464.                      end;
  465. {
  466.  
  467.   (
  468.     not
  469.     (
  470.       v1 in [1..255]
  471.     )
  472.   )
  473.   and
  474.   (
  475.     not
  476.     (
  477.       v2 in [1..255]
  478.     )
  479.   )
  480.  
  481. }
  482.                    if ((not (v1 in [1..255]))  and (not (v2 in [1..255]))) then
  483.                      begin
  484.                        xwritelnss('Bad charset conversion, must be 1..255 : ', 
  485.                                 token);
  486.                        shutdown(1);
  487.                      end;
  488.                      
  489.                   newset^.cnvtable[v1] := chr(v2);
  490.                 end;
  491.               end;
  492.  
  493.             end; { case }
  494.           end; { not comment }
  495.         end; { not blank line }
  496.  
  497.     end; { while not eof }
  498.  
  499.   close(f);
  500. end;
  501.  
  502.  
  503. procedure linetolocal;
  504.  
  505. var
  506.   i: integer;
  507.  
  508. begin
  509.  
  510.   { First, process the transferencoding }
  511.   transferdecode(s, decodingxfer);
  512.  
  513.   { Next, apply any charactersets specified }
  514.   if (decodingset <> '') then
  515.     for i:= 1 to length(s) do
  516.       s[i]:= decodingset[ord(s[i])];
  517. end;
  518.  
  519.  
  520. procedure localtoline;
  521.  
  522. var
  523.    i: integer;
  524.  
  525. begin
  526.   { Apply character set encoding }
  527.   if (encodingset <> '') then
  528.     for i:= 1 to length(s) do
  529.       s[i]:= encodingset[ord(s[i])];
  530.    
  531. { Apply transfer-encoding }
  532.   transferencode(s, encodingxfer);
  533.  
  534. end;
  535.  
  536.  
  537. begin { Module init code }
  538.  
  539.   charsets       := nil;
  540.   defaultset     := nil;
  541.  
  542.   postingsetname := 'us-ascii';
  543.   mailingsetname := 'us-ascii';
  544.   mailxfername   := '7bit';
  545.  
  546.   mailsafechars  := [#33..#60, #62..#126];
  547.  
  548. end. { Module ends }
  549.