Quellcodebibliothek Statistik Leitseite products/sources/formale Sprachen/GAP/pkg/io/gap/   (Algebra von RWTH Aachen Version 4.15.1©)  Datei vom 21.5.2025 mit Größe 13 kB image not shown  

Quelle  http.gi   Sprache: unbekannt

 
#############################################################################
##
##  http.gi               GAP 4 package IO
##                                                            Max Neunhoeffer
##
##  Copyright (C) by Max Neunhoeffer
##  This file is free software, see license information at the end.
##
##  This file contains functions implementing the client side of the
##  HTTP protocol.
##

# The following is given as argument to IO_Select for the timeout
# values in a HTTP request.

BindGlobal( "HTTPTimeoutForSelect", [fail,fail] );

InstallGlobalFunction( OpenHTTPConnection,
  function(server,port)
    local lookup,res,s;
    s := IO_socket(IO.PF_INET,IO.SOCK_STREAM,"tcp");
    if s = fail then
        return rec( sock := fail,
                    errormsg := "OpenHTTPConnection: cannot create socket" );
    fi;
    lookup := IO_gethostbyname(server);
    if lookup = fail then
        IO_close(s);
        return rec( sock := fail,
                    errormsg := "OpenHTTPConnection: cannot find hostname" );
    fi;
    res := IO_connect(s,IO_make_sockaddr_in(lookup.addr[1],port));
    if res = fail then
        IO_close(s);
        return rec( sock := fail,
                    errormsg :=
                      Concatenation("OpenHTTPConnection: cannot connect: ",
                                    LastSystemError().message) );
    fi;
    # Switch the socket to non-blocking mode, just to be sure!
    IO_fcntl(s,IO.F_SETFL,IO.O_NONBLOCK);

    return rec( sock := IO_WrapFD(s,false,false),
                errormsg := "",
                host := lookup,
                closed := false );
  end );

InstallGlobalFunction( FixChunkedBody,
  function( st )
    # This parses a chunked body and returns the final result resulting
    # from putting together the chunks. Follows rfc2616, section 3.6.1.
    # If anything goes wrong, the original is returned.
    local chunklen,head,p,pos,q,res;
    pos := 0;
    res := [];
    while true do   # will return eventually
        p := PositionSublist(st,"\r\n",pos);
        if p = fail then
            break;
        fi;
        head := st{[pos+1..p]};
        pos := p+1;
        q := 1;
        while q <= Length(head) and head[q] in "0123456789ABCDEFabcdef" do
            q := q + 1;
        od;
        chunklen := IntHexString(head{[1..q-1]});
        if chunklen = 0 then    # this was the last chunk
            break;
        else
            Add(res,st{[pos+1..pos+chunklen]});
            pos := pos + chunklen + 2;    # eat up CR and LF
        fi;
    od;
    if Length(res) > 0 then
        return Concatenation(res);
    else
        return st;
    fi;
  end );


InstallGlobalFunction( HTTPRequest,
  function(conn,method,uri,header,body,target)
    # method, uri are the strings for the first line of the request
    # header must be a record
    # body either false or a string
    # target either false or the name of a file where the body is stored
    local ParseHeader,bodyread,byt,chunk,contentlength,haveseenheader,
          inpos,k,msg,nr,out,outeof,r,responseheader,ret,w,SetError,
          chunked;

    if conn.sock = fail or conn.closed = true then
        Error("Trying to work with closed connection");
    fi;

    ParseHeader := function( out )
      # Now we want to take this apart:
      # This function modifies the variables ret and responseheader
      # in the outer function and returns the position of the first
      # byte in out after the header.
      local line,lineend,pos,pos2,pos3;
      pos := 0;
      lineend := Position(out,'\n');
      if lineend <> fail then
          if lineend >= 2 and out[lineend-1] = '\r' then
              line := out{[pos+1..lineend-2]};
          else
              line := out{[pos+1..lineend-1]};
          fi;
          ret.status := "Header corrupt";
          if line{[1..5]} = "HTTP/" and Length(line) >= 8 then
              ret.protoversion := line{[6..8]};
              pos3 := Position(line,' ');
              if pos3 <> fail then
                  pos2 := Position(line,' ',pos3);
                  if pos2 <> fail then
                      ret.statuscode := Int(line{[pos3+1..pos2-1]});
                      ret.status := line{[pos2+1..Length(line)]};
                  fi;
              fi;
          fi;
          pos := lineend;
      fi;

      while true do   # will be left by break
          lineend := Position(out,'\n',pos);
          if lineend = fail or lineend <= pos+2 then
              if lineend <> fail then pos := lineend+1; fi;
              break;   # we have seen the header
          fi;
          if out[lineend-1] = '\r' then
              line := out{[pos+1..lineend-2]};
          else
              line := out{[pos+1..lineend-1]};
          fi;
          pos2 := PositionSublist(line,": ");
          if pos2 <> fail then
              responseheader.(LowercaseString(line{[1..pos2-1]})) :=
                                      line{[pos2+2..Length(line)]};
          fi;
          pos := lineend;
      od;

      if lineend = fail then   # incomplete or corrupt header!
          return fail;
      else
          return pos;
      fi;
    end;

    # Maybe add some default values:
    if not(IsBound(header.UserAgent)) then
        header.UserAgent := Concatenation("GAP/IO/",
                                          PackageInfo("io")[1].Version);
    fi;
    if IsString(body) and Length(body) > 0 then
        header.Content\-Length := String(Length(body));
    fi;
    if not(IsBound(header.Host)) then
        header.Host := conn.host.name;
    fi;

    # Now we have a TCP connection, we can start talking:
    msg := Concatenation(method," ",uri," HTTP/1.1\r\n");
    for k in RecNames(header) do
        Append(msg,k);
        Append(msg,": ");
        Append(msg,header.(k));
        Append(msg,"\r\n");
    od;
    Append(msg,"\r\n");
    if IsString(body) then Append(msg,body); fi;

    # Here we collect first the header, then maybe the rest:
    out := "";

    # Now we have collected the complete request, we do I/O multiplexing
    # to send away everything eventually and getting back the answer:

    # Here we just do I/O multiplexing, sending away msg (if non-empty)
    # and receiving from the connection.

    # Note that we first look for the header to learn the content length:
    haveseenheader := false;

    # The answer:
    ret := rec( protoversion := "unknown",
                statuscode := 0,   # indicates an error
                status := "",      # will be filled before return
                header := fail,
                body := fail,
                closed := false );

    # The following function is used to report on errors:
    SetError := function(msg)
      # Changes the variable ret outside!
      ret.status := msg;
      ret.statuscode := 0;
      if haveseenheader then
          ret.header := responseheader;
      fi;
      if IsList(out) then
          if IsStringRep(out) then
              ret.body := out;
          else
              ret.body := Concatenation(out);
          fi;
      else
          IO_Close(out);
          ret.body := target;
      fi;
    end;

    inpos := 0;
    outeof := false;
    chunked := false;
    repeat
        if not(outeof) then
            r := [conn.sock];
        else
            r := [];
        fi;
        if inpos < Length(msg) then
            w := [conn.sock];
        else
            w := [];
        fi;
        nr := IO_Select(r,w,[],[],HTTPTimeoutForSelect[1],
                                  HTTPTimeoutForSelect[2]);
        if nr = fail then   # an error!
            SetError("Error in select, connection broken?");
            return ret;
        fi;
        if nr = 0 then      # a timeout
            SetError("Connection timed out");
            return ret;
        fi;

        # First writing:
        if Length(w) > 0 and w[1] <> fail then
            byt := IO_WriteNonBlocking(conn.sock,msg,inpos,
                        Minimum(Length(msg)-inpos,65536));
            if byt = fail and
               LastSystemError().number <> IO.EWOULDBLOCK then
                # an error occured, probably connection broken
                SetError("Connection broken");
                return ret;
            fi;
            inpos := inpos + byt;
        fi;
        # Now reading:
        if not(outeof) and r[1] <> fail then
            chunk := IO_Read(conn.sock,4096);
            if chunk = "" or chunk = fail then
                outeof := true;
                break;
            fi;

            # Otherwise it must be a non-empty string
            if not(haveseenheader) then
                Append(out,chunk);
                responseheader := rec();
                r := ParseHeader(out);
                if r <> fail then   # then it is a position number!
                    if IsBound(responseheader.transfer\-encoding) and
                       responseheader.transfer\-encoding = "chunked" then
                        chunked := true;
                        contentlength := infinity;
                        # This now only works if the server closes the
                        # connection after sending the body!
                    elif not(IsBound(responseheader.content\-length)) then
                        Print("HTTP Warning: no content length!\n");
                        contentlength := infinity;
                    else
                        if method <> "HEAD" then
                            contentlength:=Int(responseheader.content\-length);
                        else
                            contentlength := 0;
                        fi;
                    fi;
                    chunk := out{[r..Length(out)]};

                    # See to the target:
                    if IsString(target) then
                        out := IO_File(target,"w",false);
                        IO_Write(out,chunk);
                        bodyread := Length(chunk);
                    else
                        out := [chunk];
                        bodyread := Length(chunk);
                    fi;
                    haveseenheader := true;
                fi;
            else
                # We are only reading the body until done:
                if IsList(out) then
                    Add(out,chunk);
                else
                    IO_Write(out,chunk);
                fi;
                bodyread := bodyread + Length(chunk);
            fi;
        fi;
    until outeof or (haveseenheader and bodyread >= contentlength);

    if outeof and not(haveseenheader) then
        # Obviously, the connection broke:
        SetError("Connection broken");
        return ret;
    fi;

    # In the case that contentlength is infinity because it was not
    # specified and we thus read until end of file we still report
    # success! This is some tolerance against faulty servers.

    ret.closed := outeof;
    ret.header := responseheader;
    if IsList(out) then
        ret.body := Concatenation(out);
    else
        IO_Close(out);
        ret.body := target;
    fi;

    if chunked then
        # Now we have to fix everything since it came in chunks.
        if target = false then    # things are still in memory
            ret.body := FixChunkedBody(ret.body);
        else    # oops, already in a file!
            # This is a dirty hack:
            FileString(target,FixChunkedBody(StringFile(target)));
        fi;
    fi;
    return ret;
  end );

InstallGlobalFunction( CloseHTTPConnection,
  function( conn )
    IO_Close(conn.sock);
    conn.closed := true;
  end );

InstallGlobalFunction( SingleHTTPRequest,
  function(server,port,method,uri,header,body,target)
    local conn,r;
    conn := OpenHTTPConnection(server,port);
    if conn.sock = fail then
        return rec( protoversion := "unknown",
                    statuscode := 0,
                    status := conn.errormsg,
                    header := fail,
                    body := fail,
                    closed := true );
    fi;
    if not(IsBound(header.Host)) then header.Host := server; fi;
    r := HTTPRequest(conn,method,uri,header,body,target);
    CloseHTTPConnection(conn);
    r.closed := true;
    return r;
  end );

InstallGlobalFunction( ReadWeb,
  function(url)
    local p, domain, uri, f;
    # split off http://
    if Length(url)>7 and LowercaseString(url{[1..7]})="http://" then
      url:=url{[8..Length(url)]};
    fi;
    p:=Position(url,'/');
    domain:=url{[1..p-1]}; # e.g. www.gap-system.org
    uri:=url{[p..Length(url)]}; # e.g. ~xyrxmir/mystuff/bla.txt
    f:=SingleHTTPRequest(domain,80,"GET",uri,rec(),false,false);
    if f.statuscode=404 then
      Error("File not found -- Check URL");
    elif f.statuscode >= 400 then
      Error("HTTP error code ",f.statuscode);
    fi;
    f:=f.body;
    # now `f' is a string containing the file.
    Read(InputTextString(f)); # read in the contents
  end);

##
##  This program is free software: you can redistribute it and/or modify
##  it under the terms of the GNU General Public License as published by
##  the Free Software Foundation, either version 3 of the License, or
##  (at your option) any later version.
##
##  This program is distributed in the hope that it will be useful,
##  but WITHOUT ANY WARRANTY; without even the implied warranty of
##  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
##  GNU General Public License for more details.
##
##  You should have received a copy of the GNU General Public License
##  along with this program.  If not, see <https://www.gnu.org/licenses/>.
##

[ Dauer der Verarbeitung: 0.35 Sekunden  (vorverarbeitet)  ]