1{%MainUnit pas2jsfileutils.pas}
2{
3    This file is part of the Free Component Library (FCL)
4    Copyright (c) 2018  Mattias Gaertner  mattias@freepascal.org
5
6    Pascal to Javascript converter class.
7
8    See the file COPYING.FPC, included in this distribution,
9    for details about the copyright.
10
11    This program is distributed in the hope that it will be useful,
12    but WITHOUT ANY WARRANTY; without even the implied warranty of
13    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
14
15 **********************************************************************
16}
17{$IF DEFINED(UTF8_RTL) AND NOT DEFINED(WINCE)}
18  {$DEFINE ArgsWAsUTF8}
19{$ENDIF}
20
21{$IFDEF OldStuff}
22//Function prototypes
23var _ParamStrUtf8: Function(Param: Integer): string;
24{$ENDIF}
25
26var
27  ArgsW: Array of WideString;
28  ArgsWCount: Integer; // length(ArgsW)+1
29  {$IFDEF ArgsWAsUTF8}
30  ArgsUTF8: Array of String; // the ArgsW array as UTF8
31  OldArgV: PPChar = nil;
32  {$IFEND}
33
34{$ifndef wince}
35{$IFDEF OldStuff}
36function ParamStrUtf8Ansi(Param: Integer): String;
37begin
38  Result:=ObjPas.ParamStr(Param);
39end;
40{$ENDIF}
41{$endif wince}
42
43{$IFDEF OldStuff}
44function ParamStrUtf8Wide(Param: Integer): String;
45begin
46  if ArgsWCount <> ParamCount then
47  begin
48    //DebugLn('Error: ParamCount <> ArgsWCount!');
49    Result := ObjPas.ParamStr(Param);
50  end
51  else
52  begin
53    if (Param <= ArgsWCount) then
54      {$IFDEF ACP_RTL}
55      Result := String(UnicodeString(ArgsW[Param]))
56      {$ELSE}
57      Result := UTF16ToUTF8(ArgsW[Param])
58      {$ENDIF ACP_RTL}
59    else
60      Result := '';
61  end;
62end;
63{$ENDIF oldstuff}
64
65{$IFDEF ArgsWAsUTF8}
66procedure SetupArgvAsUtf8;
67var
68  i: Integer;
69begin
70  SetLength(ArgsUTF8,length(ArgsW));
71  OldArgV:=argv;
72  GetMem(argv,SizeOf(Pointer)*length(ArgsW));
73  for i:=0 to length(ArgsW)-1 do
74  begin
75    ArgsUTF8[i]:=ArgsW{%H-}[i];
76    argv[i]:=PChar(ArgsUTF8[i]);
77  end;
78end;
79{$endif}
80
81procedure SetupCommandlineParametersWide;
82var
83  ArgLen, Start, CmdLen, i, j: SizeInt;
84  Quote   : Boolean;
85  Buf: array[0..259] of WChar;  // need MAX_PATH bytes, not 256!
86  PCmdLineW: PWideChar;
87  CmdLineW: WideString;
88
89  procedure AllocArg(Idx, Len:longint);
90  begin
91    if (Idx >= ArgsWCount) then
92      SetLength(ArgsW, Idx + 1);
93    SetLength(ArgsW[Idx], Len);
94  end;
95
96begin
97  { create commandline, it starts with the executed filename which is argv[0] }
98  { Win32 passes the command NOT via the args, but via getmodulefilename}
99  ArgsWCount := 0;
100  ArgLen := GetModuleFileNameW(0, @buf[0], sizeof(buf));
101
102  //writeln('ArgLen = ',Arglen);
103
104  buf[ArgLen] := #0; // be safe, no terminating 0 on XP
105  allocarg(0,arglen);
106  move(buf[0],ArgsW[0][1],arglen * SizeOf(WChar));
107
108  //writeln('ArgsW[0] = ',ArgsW[0]);
109
110  PCmdLineW := nil;
111  { Setup cmdline variable }
112  PCmdLineW := GetCommandLineW;
113  CmdLen := StrLen(PCmdLineW);
114
115  //writeln('StrLen(PCmdLineW) = ',CmdLen);
116
117  SetLength(CmdLineW, CmdLen);
118  Move(PCmdLineW^, CmdLineW[1], CmdLen * SizeOf(WChar));
119
120
121  //debugln(CmdLineW);
122  //for i := 1 to CmdLen do DbgOut(DbgS(i mod 10)); debugln;
123
124  i := 1;
125  while (i <= CmdLen) do
126  begin
127    //debugln('Next');
128    //DbgOut('i=',DbgS(i),' CmdLineW[',DbgS(i),']=');if i<=CmdLen then debugln(CmdLineW[i]) else debugln('#0');
129    //skip leading spaces
130    while (i <= CmdLen) and (CmdLineW[i] <= #32) do Inc(i);
131    //DbgOut('After skipping spaces: i=',DbgS(i),' CmdLineW[',DbgS(i),']=');if i<=CmdLen then debugln(CmdLineW[i]) else debugln('#0');
132    if (i > CmdLen) then Break;
133    Quote := False;
134    Start := i;
135    ArgLen := 0;
136    while (i <= CmdLen) do
137    begin //find next commandline parameter
138      case CmdLineW[i] of
139        #1..#32:
140        begin
141          if Quote then
142          begin
143            //debugln('i=',DbgS(i),': Space in Quote');
144            Inc(ArgLen)
145          end
146          else
147          begin
148            //debugln('i=',DbgS(i),': Space in NOT Quote');
149            Break;
150          end;
151        end;
152        '"':
153        begin
154          if (i < CmdLen) and (CmdLineW[i+1] <> '"') then
155          begin
156            //debugln('i=',DbgS(i),': Quote := not Quote');
157            Quote := not Quote
158          end
159          else
160          begin
161            //debugln('i=',DbgS(i),': Skip Quote');
162            Inc(i);
163          end;
164        end;
165        else Inc(ArgLen);
166      end;//case
167      Inc(i);
168    end; //find next commandline parameter
169
170    //debugln('ArgWCount=',DbgS(ArgsWCount),' Start=',DbgS(start),' ArgLen=',DbgS(arglen),' i=',DbgS(i));
171
172    //we already have (a better) ArgW[0]
173    if (ArgsWCount > 0) then
174    begin //Process commandline parameter
175      AllocArg(ArgsWCount, ArgLen);
176      Quote := False;
177      i := Start;
178      j := 1;
179      while (i <= CmdLen) do
180      begin
181        case CmdLineW[i] of
182          #1..#32:
183          begin
184            if Quote then
185            begin
186              //if j > ArgLen then debugln('Error whitespace: j > ArgLen: j=',DbgS(j),' ArgLen=',DbgS(arglen));
187              ArgsW[ArgsWCount][j] := CmdLineW[i];
188              Inc(j);
189            end
190            else
191              Break;
192          end;
193          '"':
194          begin
195            if (i < CmdLen) and (CmdLineW[i+1] <> '"') then
196              Quote := not Quote
197            else
198              Inc(i);
199          end;
200          else
201          begin
202            //if j > ArgLen then debugln('Error Quote: j > ArgLen: j=',DbgS(j),' ArgLen=',DbgS(arglen));
203            ArgsW[ArgsWCount][j] := CmdLineW[i];
204            Inc(j);
205          end;
206        end;
207        Inc(i);
208      end;
209
210      //debugln('ArgsW[',DbgS(ArgsWCount),'] = ',ArgsW[ArgsWCount]);
211    end; // Process commandline parameter
212    Inc(ArgsWCount);
213
214  end;
215  Dec(ArgsWCount);
216  //Note:
217  //On WinCe Argsv is a static function, so we cannot change it.
218  //This might change in the future if Argsv on WinCE will be declared as a function variable
219  {$IFDEF ArgsWAsUTF8}
220  if DefaultSystemCodePage=CP_UTF8 then
221    SetupArgvAsUtf8;
222  {$IFEND}
223end;
224
225function FilenameIsAbsolute(const aFilename: string): boolean;
226begin
227  Result:=FilenameIsWinAbsolute(aFilename);
228end;
229
230procedure GetDirUtf8(DriveNr: Byte; var Dir: String);
231{This procedure may not be threadsafe, because SetCurrentDirectory isn't}
232{$ifndef WinCE}
233var
234  w, D: WideString;
235  SavedDir: WideString;
236  res : Integer;
237{$endif}
238begin
239  {$ifdef WinCE}
240  Dir := '\';
241  // Previously we sent an exception here, which is correct, but this causes
242  // trouble with code which isnt tested for WinCE, so lets just send a dummy result instead
243  // Exception.Create('[GetCurrentDirWide] The concept of the current directory doesn''t exist in Windows CE');
244  {$else}
245  //writeln('GetDirWide START');
246  if not (DriveNr = 0) then
247  begin
248    res := GetCurrentDirectoryW(0, nil);
249    SetLength(SavedDir, res);
250    res:=Windows.GetCurrentDirectoryW(res, @SavedDir[1]);
251    SetLength(SavedDir,res);
252
253    D := WideChar(64 + DriveNr) + ':';
254    if not SetCurrentDirectoryW(@D[1]) then
255    begin
256      Dir := Char(64 + DriveNr) + ':\';
257      SetCurrentDirectoryW(@SavedDir[1]);
258      Exit;
259    end;
260  end;
261  res := GetCurrentDirectoryW(0, nil);
262  SetLength(w, res);
263  res := GetCurrentDirectoryW(res, @w[1]);
264  SetLength(w, res);
265  Dir:=UTF16ToUTF8(w);
266  if not (DriveNr = 0) then SetCurrentDirectoryW(@SavedDir[1]);
267  //writeln('GetDirWide END');
268  {$endif}
269end;
270
271function ExpandFileNamePJ(const FileName: string; {const} BaseDir: String = ''): String;
272var
273  IsAbs, StartsWithRoot, CanUseBaseDir : Boolean;
274  {$ifndef WinCE}
275  HasDrive: Boolean;
276  FnDrive, CurDrive, BaseDirDrive: Char;
277  {$endif}
278  CurDir, Fn: String;
279begin
280  //writeln('LazFileUtils.ExpandFileNamePJ');
281  //writeln('FileName = "',FileName,'"');
282  //writeln('BaseDir  = "',BaseDir,'"');
283
284  Fn := FileName;
285  //if Filename uses ExtendedLengthPath scheme then it cannot be expanded
286  //AND it should not be altered by ForcePathDelims or ResolveDots
287  //See: http://msdn.microsoft.com/en-us/library/windows/desktop/aa365247%28v=vs.85%29.aspx
288  if (Length(Fn) > 3) and (Fn[1] = PathDelim) and (Fn[2] = PathDelim) and
289     (Fn[3] = '?') and (Fn[4] = PathDelim) //Do NOT use AllowDirectorySeparators here!
290     then Exit(FN);
291  ForcePathDelims(Fn);
292  IsAbs := FileNameIsWinAbsolute(Fn);
293  if not IsAbs then
294  begin
295    StartsWithRoot := (Fn = '\') or
296                      ((Length(Fn) > 1) and
297                      (Fn[1] = DirectorySeparator) and
298                      (Fn[2] <> DirectorySeparator));
299    {$ifndef WinCE}
300    HasDrive := (Length(Fn) > 1) and
301                (Fn[2] = ':') and
302                (UpCase(Fn[1]) in ['A'..'Z']);
303
304    if HasDrive then
305    begin
306      FnDrive := UpCase(Fn[1]);
307      GetDirUtf8(Byte(FnDrive)-64, CurDir{%H-});
308      CurDrive := UpCase(GetCurrentDirPJ[1]);
309    end
310    else
311    begin
312      CurDir := GetCurrentDirPJ;
313      FnDrive := UpCase(CurDir[1]);
314      CurDrive := FnDrive;
315    end;
316
317    //writeln('HasDrive = ',HasDrive,' Fn = ',Fn);
318    //writeln('CurDir = ',CurDir);
319    //writeln('CurDrive = ',CurDrive);
320    //writeln('FnDrive  = ',FnDrive);
321
322    if (Length(BaseDir) > 1) and (UpCase(BaseDir[1]) in ['A'..'Z']) and (BaseDir[2] = ':') then
323    begin
324      BaseDirDrive := BaseDir[1]
325    end
326    else
327    begin
328      if HasDrive then
329        BaseDirDrive := CurDrive
330      else
331        BaseDirDrive := #0;
332    end;
333
334    //You cannot use BaseDir if both FileName and BaseDir includes a drive and they are not the same
335    CanUseBaseDir := ((BaseDirDrive = #0) or
336                     (not HasDrive) or
337                     (HasDrive and (FnDrive = BaseDirDrive)))
338                     and (BaseDir <> '');
339
340    //writeln('CanUseBaseDir = ',CanUseBaseDir);
341
342    if not HasDrive and StartsWithRoot and not CanUseBaseDir then
343    begin
344      //writeln('HasDrive and StartsWithRoot');
345      Fn := Copy(CurDir,1,2) + Fn;
346      HasDrive := True;
347      IsAbs := True;
348    end;
349    //FileNames like C:foo, strip Driveletter + colon
350    if HasDrive and not IsAbs then Delete(Fn,1,2);
351
352    //writeln('HasDrive = ',Hasdrive,' Fn = ',Fn);
353    {$else}
354    CanUseBaseDir := True;
355    {$endif WinCE}
356  end;
357  if IsAbs then
358  begin
359    //writeln('IsAbs = True -> Exit');
360    Result := ResolveDots(Fn);
361  end
362  else
363  begin
364    if not CanUseBaseDir or (BaseDir = '') then
365      Fn := IncludeTrailingPathDelimiter(CurDir) + Fn
366    else
367    begin
368      if (Length(Fn) > 0) and (Fn[1] = DirectorySeparator) then Delete(Fn,1,1);
369      Fn := IncludeTrailingPathDelimiter(BaseDir) + Fn;
370    end;
371
372    Fn := ResolveDots(Fn);
373    //if BaseDir is something like 'z:foo\' or '\' then this needs to be expanded as well
374    if not FileNameIsAbsolute(Fn) then
375      Fn := ExpandFileNamePJ(Fn, '');
376    Result := Fn;
377  end;
378end;
379
380function GetCurrentDirPJ: String;
381{$ifndef WinCE}
382var
383  w   : UnicodeString;
384  res : Integer;
385  {$endif}
386begin
387  {$ifdef WinCE}
388  Result := '\';
389  // Previously we sent an exception here, which is correct, but this causes
390  // trouble with code which isnt tested for WinCE, so lets just send a dummy result instead
391  // Exception.Create('[GetCurrentDirWide] The concept of the current directory doesn''t exist in Windows CE');
392  {$else}
393  res:=GetCurrentDirectoryW(0, nil);
394  SetLength(w, res);
395  res:=Windows.GetCurrentDirectoryW(res, @w[1]);
396  SetLength(w, res);
397  Result:=UTF16ToUTF8(w);
398  {$endif}
399end;
400
401function GetPhysicalFilename(const Filename: string; ExceptionOnError: boolean
402  ): string;
403begin
404  Result:=Filename;
405  if ExceptionOnError then ;
406end;
407
408function ResolveSymLinks(const Filename: string; ExceptionOnError: boolean
409  ): string;
410begin
411  Result:=Filename;
412end;
413
414function IsUNCPath(const Path: String): Boolean;
415begin
416  Result := (Length(Path) > 2)
417    and (Path[1] in AllowDirectorySeparators)
418    and (Path[2] in AllowDirectorySeparators);
419end;
420
421function ExtractUNCVolume(const Path: String): String;
422var
423  I, Len: Integer;
424
425  // the next function reuses Len variable
426  function NextPathDelim(const Start: Integer): Integer;// inline;
427  begin
428    Result := Start;
429    while (Result <= Len) and not (Path[Result] in AllowDirectorySeparators) do
430      inc(Result);
431  end;
432
433begin
434  if not IsUNCPath(Path) then
435    Exit('');
436  I := 3;
437  Len := Length(Path);
438  if Path[I] = '?' then
439  begin
440    // Long UNC path form like:
441    // \\?\UNC\ComputerName\SharedFolder\Resource or
442    // \\?\C:\Directory
443    inc(I);
444    if not (Path[I] in AllowDirectorySeparators) then
445      Exit('');
446    if UpperCase(Copy(Path, I + 1, 3)) = 'UNC' then
447    begin
448      inc(I, 4);
449      if I < Len then
450        I := NextPathDelim(I + 1);
451      if I < Len then
452        I := NextPathDelim(I + 1);
453    end;
454  end
455  else
456  begin
457    I := NextPathDelim(I);
458    if I < Len then
459      I := NextPathDelim(I + 1);
460  end;
461  Result := Copy(Path, 1, I);
462end;
463
464function FileGetAttrUTF8(const FileName: String): Longint;
465begin
466  Result:=Integer(Windows.GetFileAttributesW(PWideChar(UTF8Decode(FileName))));
467end;
468
469function FileIsWritable(const AFilename: string): boolean;
470begin
471  Result:=((FileGetAttrUTF8(AFilename) and faReadOnly) = 0);
472end;
473
474function FileIsExecutable(const AFilename: string): boolean;
475begin
476  Result:=FileExists(AFilename);
477end;
478
479function GetEnvironmentVariableCountPJ: Integer;
480var
481  hp,p : PWideChar;
482begin
483  Result:=0;
484  p:=GetEnvironmentStringsW;
485  if p=nil then exit;
486  hp:=p;
487  while hp^<>#0 do
488  begin
489    Inc(Result);
490    hp:=hp+strlen(hp)+1;
491  end;
492  FreeEnvironmentStringsW(p);
493end;
494
495function GetEnvironmentStringPJ(Index: Integer): string;
496var
497  hp,p : PWideChar;
498begin
499  Result:='';
500  p:=GetEnvironmentStringsW;
501  if p=nil then exit;
502  hp:=p;
503  while (hp^<>#0) and (Index>1) do
504  begin
505    Dec(Index);
506    hp:=hp+strlen(hp)+1;
507  end;
508  if (hp^<>#0) then
509    Result:=UTF16ToUTF8(hp);
510  FreeEnvironmentStringsW(p);
511end;
512
513function GetEnvironmentVariablePJ(const EnvVar: string): String;
514begin
515  Result:=UTF16ToUTF8(SysUtils.GetEnvironmentVariable(UTF8ToUTF16(EnvVar)));
516end;
517
518// AConsole - If false, it is the general system encoding,
519//            if true, it is the console encoding
520function GetWindowsEncoding(AConsole: Boolean = False): string;
521var
522  cp : UINT;
523{$IFDEF WinCE}
524// CP_UTF8 is missing in the windows unit of the Windows CE RTL
525const
526  CP_UTF8 = 65001;
527{$ENDIF}
528begin
529  if AConsole then cp := GetOEMCP
530  else cp := GetACP;
531
532  case cp of
533    CP_UTF8: Result := EncodingUTF8;
534  else
535    Result:='cp'+IntToStr(cp);
536  end;
537end;
538
539function GetConsoleTextEncoding: string;
540begin
541  Result:=GetWindowsEncoding(True);
542end;
543
544{$ifdef WinCe}
545function UTF8ToSystemCP(const s: string): string; inline;
546begin
547  Result := s;
548end;
549{$else}
550function UTF8ToSystemCP(const s: string): string;
551// result has codepage CP_ACP
552var
553  src: UnicodeString;
554  len: LongInt;
555begin
556  Result:=s;
557  if IsASCII(Result) then
558  begin
559    // prevent codepage conversion magic
560    SetCodePage(RawByteString(Result), CP_ACP, False);
561    exit;
562  end;
563  src:=UTF8Decode(s);
564  if src='' then
565    exit;
566  len:=WideCharToMultiByte(CP_ACP,0,PUnicodeChar(src),length(src),nil,0,nil,nil);
567  SetLength(Result,len);
568  if len>0 then
569  begin
570    WideCharToMultiByte(CP_ACP,0,PUnicodeChar(src),length(src),@Result[1],length(Result),nil,nil);
571    // prevent codepage conversion magic
572    SetCodePage(RawByteString(Result), CP_ACP, False);
573  end;
574end;
575{$endif not wince}
576
577{$ifdef WinCE}
578function SystemCPToUTF8(const s: string): string; inline;
579begin
580  Result := SysToUtf8(s);
581end;
582{$else}
583// for all Windows supporting 8bit codepages (e.g. not WinCE)
584function SystemCPToUTF8(const s: string): string;
585// result has codepage CP_ACP
586var
587  UTF16WordCnt: SizeInt;
588  UTF16Str: UnicodeString;
589begin
590  Result:=s;
591  if IsASCII(Result) then
592  begin
593    // prevent codepage conversion magic
594    SetCodePage(RawByteString(Result), CP_ACP, False);
595    exit;
596  end;
597  UTF16WordCnt:=MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, Pointer(s), length(s), nil, 0);
598  // this will null-terminate
599  if UTF16WordCnt>0 then
600  begin
601    setlength(UTF16Str, UTF16WordCnt);
602    MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, Pointer(s), length(s), @UTF16Str[1], UTF16WordCnt);
603    Result:=UTF16ToUTF8(UTF16Str);
604  end;
605end;
606{$endif not wince}
607
608{$ifdef WinCe}
609function UTF8ToConsole(const s: string): string; // converts UTF8 to console string (used by Write, WriteLn)
610begin
611  Result := UTF8ToSystemCP(s);
612end;
613{$else}
614function UTF8ToConsole(const s: string): string; // converts UTF8 to console string (used by Write, WriteLn)
615var
616  Dst: PChar;
617begin
618  {$ifndef NO_CP_RTL}
619  Result := UTF8ToSystemCP(s);
620  {$else NO_CP_RTL}
621  Result := s; // Kept for compatibility
622  {$endif NO_CP_RTL}
623  Dst := AllocMem((Length(Result) + 1) * SizeOf(Char));
624  if CharToOEM(PChar(Result), Dst) then
625    Result := StrPas(Dst);
626  FreeMem(Dst);
627  {$ifndef NO_CP_RTL}
628  SetCodePage(RawByteString(Result), CP_OEMCP, False);
629  {$endif NO_CP_RTL}
630end;
631{$endif not WinCE}
632
633{$ifdef WinCE}
634function ConsoleToUTF8(const s: string): string;// converts console encoding to UTF8
635begin
636  Result := SysToUTF8(s);
637end;
638{$else}
639function ConsoleToUTF8(const s: string): string;// converts console encoding to UTF8
640var
641  Dst: PChar;
642begin
643  Dst := AllocMem((Length(s) + 1) * SizeOf(Char));
644  if OemToChar(PChar(s), Dst) then
645    Result := StrPas(Dst)
646  else
647    Result := s;
648  FreeMem(Dst);
649  Result := SystemCPToUTF8(Result);
650end;
651{$endif not wince}
652
653procedure InitPlatform;
654begin
655  {$ifndef WinCE}
656  if Win32MajorVersion <= 4 then
657  begin
658    {$IFDEF OldStuff}
659    _ParamStrUtf8 := @ParamStrUtf8Ansi;
660    {$ENDIF}
661  end
662  else
663  {$endif}
664  begin
665    ArgsWCount := -1;
666    {$IFDEF OldStuff}
667    _ParamStrUtf8 := @ParamStrUtf8Wide;
668    {$ENDIF}
669    SetupCommandlineParametersWide;
670  end;
671end;
672
673procedure FinalizePlatform;
674{$IFDEF ArgsWAsUTF8}
675var
676  p: PPChar;
677{$ENDIF}
678begin
679  {$IFDEF ArgsWAsUTF8}
680  // restore argv and free memory
681  if OldArgV<>nil then
682  begin
683    p:=argv;
684    argv:=OldArgV;
685    Freemem(p);
686  end;
687  {$ENDIF}
688end;
689