1 /*
   2  * Copyright (c) 2003, 2014, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 #include <jni.h>
  27 #include <jni_util.h>
  28 #include <jvm_md.h>
  29 #include <dlfcn.h>
  30 #include <cups/cups.h>
  31 #include <cups/ppd.h>
  32 /*
  33  * CUPS #define's __attribute__(x) to be empty unless __GNUC__ is defined.
  34  * However OpenJDK officially uses the SunStudio compiler on Solaris.
  35  * We need to #undef this else it breaks use of this keyword used by JNIEXPORT.
  36  * See: https://github.com/apple/cups/issues/5349
  37  */
  38 #ifdef __SUNPRO_C
  39 #undef __attribute__
  40 #endif
  41 
  42 
  43 //#define CUPS_DEBUG
  44 
  45 #ifdef CUPS_DEBUG
  46 #define DPRINTF(x, y) fprintf(stderr, x, y);
  47 #else
  48 #define DPRINTF(x, y)
  49 #endif
  50 
  51 typedef const char* (*fn_cupsServer)(void);
  52 typedef int (*fn_ippPort)(void);
  53 typedef http_t* (*fn_httpConnect)(const char *, int);
  54 typedef void (*fn_httpClose)(http_t *);
  55 typedef char* (*fn_cupsGetPPD)(const char *);
  56 typedef cups_dest_t* (*fn_cupsGetDest)(const char *name,
  57     const char *instance, int num_dests, cups_dest_t *dests);
  58 typedef int (*fn_cupsGetDests)(cups_dest_t **dests);
  59 typedef void (*fn_cupsFreeDests)(int num_dests, cups_dest_t *dests);
  60 typedef ppd_file_t* (*fn_ppdOpenFile)(const char *);
  61 typedef void (*fn_ppdClose)(ppd_file_t *);
  62 typedef ppd_option_t* (*fn_ppdFindOption)(ppd_file_t *, const char *);
  63 typedef ppd_size_t* (*fn_ppdPageSize)(ppd_file_t *, char *);
  64 
  65 fn_cupsServer j2d_cupsServer;
  66 fn_ippPort j2d_ippPort;
  67 fn_httpConnect j2d_httpConnect;
  68 fn_httpClose j2d_httpClose;
  69 fn_cupsGetPPD j2d_cupsGetPPD;
  70 fn_cupsGetDest j2d_cupsGetDest;
  71 fn_cupsGetDests j2d_cupsGetDests;
  72 fn_cupsFreeDests j2d_cupsFreeDests;
  73 fn_ppdOpenFile j2d_ppdOpenFile;
  74 fn_ppdClose j2d_ppdClose;
  75 fn_ppdFindOption j2d_ppdFindOption;
  76 fn_ppdPageSize j2d_ppdPageSize;
  77 
  78 
  79 /*
  80  * Initialize library functions.
  81  * // REMIND : move tab , add dlClose before return
  82  */
  83 JNIEXPORT jboolean JNICALL
  84 Java_sun_print_CUPSPrinter_initIDs(JNIEnv *env,
  85                                          jobject printObj) {
  86   void *handle = dlopen(VERSIONED_JNI_LIB_NAME("cups", "2"),
  87                         RTLD_LAZY | RTLD_GLOBAL);
  88 
  89   if (handle == NULL) {
  90     handle = dlopen(JNI_LIB_NAME("cups"), RTLD_LAZY | RTLD_GLOBAL);
  91     if (handle == NULL) {
  92       return JNI_FALSE;
  93     }
  94   }
  95 
  96   j2d_cupsServer = (fn_cupsServer)dlsym(handle, "cupsServer");
  97   if (j2d_cupsServer == NULL) {
  98     dlclose(handle);
  99     return JNI_FALSE;
 100   }
 101 
 102   j2d_ippPort = (fn_ippPort)dlsym(handle, "ippPort");
 103   if (j2d_ippPort == NULL) {
 104     dlclose(handle);
 105     return JNI_FALSE;
 106   }
 107 
 108   j2d_httpConnect = (fn_httpConnect)dlsym(handle, "httpConnect");
 109   if (j2d_httpConnect == NULL) {
 110     dlclose(handle);
 111     return JNI_FALSE;
 112   }
 113 
 114   j2d_httpClose = (fn_httpClose)dlsym(handle, "httpClose");
 115   if (j2d_httpClose == NULL) {
 116     dlclose(handle);
 117     return JNI_FALSE;
 118   }
 119 
 120   j2d_cupsGetPPD = (fn_cupsGetPPD)dlsym(handle, "cupsGetPPD");
 121   if (j2d_cupsGetPPD == NULL) {
 122     dlclose(handle);
 123     return JNI_FALSE;
 124   }
 125 
 126   j2d_cupsGetDest = (fn_cupsGetDest)dlsym(handle, "cupsGetDest");
 127   if (j2d_cupsGetDest == NULL) {
 128     dlclose(handle);
 129     return JNI_FALSE;
 130   }
 131 
 132   j2d_cupsGetDests = (fn_cupsGetDests)dlsym(handle, "cupsGetDests");
 133   if (j2d_cupsGetDests == NULL) {
 134     dlclose(handle);
 135     return JNI_FALSE;
 136   }
 137 
 138   j2d_cupsFreeDests = (fn_cupsFreeDests)dlsym(handle, "cupsFreeDests");
 139   if (j2d_cupsFreeDests == NULL) {
 140     dlclose(handle);
 141     return JNI_FALSE;
 142   }
 143 
 144   j2d_ppdOpenFile = (fn_ppdOpenFile)dlsym(handle, "ppdOpenFile");
 145   if (j2d_ppdOpenFile == NULL) {
 146     dlclose(handle);
 147     return JNI_FALSE;
 148 
 149   }
 150 
 151   j2d_ppdClose = (fn_ppdClose)dlsym(handle, "ppdClose");
 152   if (j2d_ppdClose == NULL) {
 153     dlclose(handle);
 154     return JNI_FALSE;
 155 
 156   }
 157 
 158   j2d_ppdFindOption = (fn_ppdFindOption)dlsym(handle, "ppdFindOption");
 159   if (j2d_ppdFindOption == NULL) {
 160     dlclose(handle);
 161     return JNI_FALSE;
 162   }
 163 
 164   j2d_ppdPageSize = (fn_ppdPageSize)dlsym(handle, "ppdPageSize");
 165   if (j2d_ppdPageSize == NULL) {
 166     dlclose(handle);
 167     return JNI_FALSE;
 168   }
 169 
 170   return JNI_TRUE;
 171 }
 172 
 173 /*
 174  * Gets CUPS server name.
 175  *
 176  */
 177 JNIEXPORT jstring JNICALL
 178 Java_sun_print_CUPSPrinter_getCupsServer(JNIEnv *env,
 179                                          jobject printObj)
 180 {
 181     jstring cServer = NULL;
 182     const char* server = j2d_cupsServer();
 183     if (server != NULL) {
 184         // Is this a local domain socket?
 185         if (strncmp(server, "/", 1) == 0) {
 186             cServer = JNU_NewStringPlatform(env, "localhost");
 187         } else {
 188             cServer = JNU_NewStringPlatform(env, server);
 189         }
 190     }
 191     return cServer;
 192 }
 193 
 194 /*
 195  * Gets CUPS port name.
 196  *
 197  */
 198 JNIEXPORT jint JNICALL
 199 Java_sun_print_CUPSPrinter_getCupsPort(JNIEnv *env,
 200                                          jobject printObj)
 201 {
 202     int port = j2d_ippPort();
 203     return (jint) port;
 204 }
 205 
 206 
 207 /*
 208  * Gets CUPS default printer name.
 209  *
 210  */
 211 JNIEXPORT jstring JNICALL
 212 Java_sun_print_CUPSPrinter_getCupsDefaultPrinter(JNIEnv *env,
 213                                                   jobject printObj)
 214 {
 215     jstring cDefPrinter = NULL;
 216     cups_dest_t *dests;
 217     char *defaultPrinter = NULL;
 218     int num_dests = j2d_cupsGetDests(&dests);
 219     int i = 0;
 220     cups_dest_t *dest = j2d_cupsGetDest(NULL, NULL, num_dests, dests);
 221     if (dest != NULL) {
 222         defaultPrinter = dest->name;
 223         if (defaultPrinter != NULL) {
 224             cDefPrinter = JNU_NewStringPlatform(env, defaultPrinter);
 225         }
 226     }
 227     j2d_cupsFreeDests(num_dests, dests);
 228     return cDefPrinter;
 229 }
 230 
 231 /*
 232  * Checks if connection can be made to the server.
 233  *
 234  */
 235 JNIEXPORT jboolean JNICALL
 236 Java_sun_print_CUPSPrinter_canConnect(JNIEnv *env,
 237                                       jobject printObj,
 238                                       jstring server,
 239                                       jint port)
 240 {
 241     const char *serverName;
 242     serverName = (*env)->GetStringUTFChars(env, server, NULL);
 243     if (serverName != NULL) {
 244         http_t *http = j2d_httpConnect(serverName, (int)port);
 245         (*env)->ReleaseStringUTFChars(env, server, serverName);
 246         if (http != NULL) {
 247             j2d_httpClose(http);
 248             return JNI_TRUE;
 249         }
 250     }
 251     return JNI_FALSE;
 252 }
 253 
 254 
 255 /*
 256  * Returns list of media: pages + trays
 257  */
 258 JNIEXPORT jobjectArray JNICALL
 259 Java_sun_print_CUPSPrinter_getMedia(JNIEnv *env,
 260                                          jobject printObj,
 261                                          jstring printer)
 262 {
 263     ppd_file_t *ppd;
 264     ppd_option_t *optionTray, *optionPage;
 265     ppd_choice_t *choice;
 266     const char *name;
 267     const char *filename;
 268     int i, nTrays=0, nPages=0, nTotal=0;
 269     jstring utf_str;
 270     jclass cls;
 271     jobjectArray nameArray = NULL;
 272 
 273     name = (*env)->GetStringUTFChars(env, printer, NULL);
 274     if (name == NULL) {
 275         (*env)->ExceptionClear(env);
 276         JNU_ThrowOutOfMemoryError(env, "Could not create printer name");
 277         return NULL;
 278     }
 279 
 280     // NOTE: cupsGetPPD returns a pointer to a filename of a temporary file.
 281     // unlink() must be caled to remove the file when finished using it.
 282     filename = j2d_cupsGetPPD(name);
 283     (*env)->ReleaseStringUTFChars(env, printer, name);
 284     CHECK_NULL_RETURN(filename, NULL);
 285 
 286     cls = (*env)->FindClass(env, "java/lang/String");
 287     CHECK_NULL_RETURN(cls, NULL);
 288 
 289     if ((ppd = j2d_ppdOpenFile(filename)) == NULL) {
 290         unlink(filename);
 291         DPRINTF("CUPSfuncs::unable to open PPD  %s\n", filename);
 292         return NULL;
 293     }
 294 
 295     optionPage = j2d_ppdFindOption(ppd, "PageSize");
 296     if (optionPage != NULL) {
 297         nPages = optionPage->num_choices;
 298     }
 299 
 300     optionTray = j2d_ppdFindOption(ppd, "InputSlot");
 301     if (optionTray != NULL) {
 302         nTrays = optionTray->num_choices;
 303     }
 304 
 305     if ((nTotal = (nPages+nTrays) *2) > 0) {
 306         nameArray = (*env)->NewObjectArray(env, nTotal, cls, NULL);
 307         if (nameArray == NULL) {
 308             unlink(filename);
 309             j2d_ppdClose(ppd);
 310             DPRINTF("CUPSfuncs::bad alloc new array\n", "")
 311             (*env)->ExceptionClear(env);
 312             JNU_ThrowOutOfMemoryError(env, "OutOfMemoryError");
 313             return NULL;
 314         }
 315 
 316         for (i = 0; optionPage!=NULL && i<nPages; i++) {
 317             choice = (optionPage->choices)+i;
 318             utf_str = JNU_NewStringPlatform(env, choice->text);
 319             if (utf_str == NULL) {
 320                 unlink(filename);
 321                 j2d_ppdClose(ppd);
 322                 DPRINTF("CUPSfuncs::bad alloc new string ->text\n", "")
 323                 JNU_ThrowOutOfMemoryError(env, "OutOfMemoryError");
 324                 return NULL;
 325             }
 326             (*env)->SetObjectArrayElement(env, nameArray, i*2, utf_str);
 327             (*env)->DeleteLocalRef(env, utf_str);
 328             utf_str = JNU_NewStringPlatform(env, choice->choice);
 329             if (utf_str == NULL) {
 330                 unlink(filename);
 331                 j2d_ppdClose(ppd);
 332                 DPRINTF("CUPSfuncs::bad alloc new string ->choice\n", "")
 333                 JNU_ThrowOutOfMemoryError(env, "OutOfMemoryError");
 334                 return NULL;
 335             }
 336             (*env)->SetObjectArrayElement(env, nameArray, i*2+1, utf_str);
 337             (*env)->DeleteLocalRef(env, utf_str);
 338         }
 339 
 340         for (i = 0; optionTray!=NULL && i<nTrays; i++) {
 341             choice = (optionTray->choices)+i;
 342             utf_str = JNU_NewStringPlatform(env, choice->text);
 343             if (utf_str == NULL) {
 344                 unlink(filename);
 345                 j2d_ppdClose(ppd);
 346                 DPRINTF("CUPSfuncs::bad alloc new string text\n", "")
 347                 JNU_ThrowOutOfMemoryError(env, "OutOfMemoryError");
 348                 return NULL;
 349             }
 350             (*env)->SetObjectArrayElement(env, nameArray,
 351                                           (nPages+i)*2, utf_str);
 352             (*env)->DeleteLocalRef(env, utf_str);
 353             utf_str = JNU_NewStringPlatform(env, choice->choice);
 354             if (utf_str == NULL) {
 355                 unlink(filename);
 356                 j2d_ppdClose(ppd);
 357                 DPRINTF("CUPSfuncs::bad alloc new string choice\n", "")
 358                 JNU_ThrowOutOfMemoryError(env, "OutOfMemoryError");
 359                 return NULL;
 360             }
 361             (*env)->SetObjectArrayElement(env, nameArray,
 362                                           (nPages+i)*2+1, utf_str);
 363             (*env)->DeleteLocalRef(env, utf_str);
 364         }
 365     }
 366     j2d_ppdClose(ppd);
 367     unlink(filename);
 368     return nameArray;
 369 }
 370 
 371 
 372 /*
 373  * Returns list of page sizes and imageable area.
 374  */
 375 JNIEXPORT jfloatArray JNICALL
 376 Java_sun_print_CUPSPrinter_getPageSizes(JNIEnv *env,
 377                                          jobject printObj,
 378                                          jstring printer)
 379 {
 380     ppd_file_t *ppd;
 381     ppd_option_t *option;
 382     ppd_choice_t *choice;
 383     ppd_size_t *size;
 384     const char *filename = NULL;
 385     int i;
 386     jobjectArray sizeArray = NULL;
 387     jfloat *dims;
 388 
 389     const char *name = (*env)->GetStringUTFChars(env, printer, NULL);
 390     if (name == NULL) {
 391         (*env)->ExceptionClear(env);
 392         JNU_ThrowOutOfMemoryError(env, "Could not create printer name");
 393         return NULL;
 394     }
 395 
 396     // NOTE: cupsGetPPD returns a pointer to a filename of a temporary file.
 397     // unlink() must be called to remove the file after using it.
 398     filename = j2d_cupsGetPPD(name);
 399     (*env)->ReleaseStringUTFChars(env, printer, name);
 400     CHECK_NULL_RETURN(filename, NULL);
 401     if ((ppd = j2d_ppdOpenFile(filename)) == NULL) {
 402         unlink(filename);
 403         DPRINTF("unable to open PPD  %s\n", filename)
 404         return NULL;
 405     }
 406     option = j2d_ppdFindOption(ppd, "PageSize");
 407     if (option != NULL && option->num_choices > 0) {
 408         // create array of dimensions - (num_choices * 6)
 409         //to cover length & height
 410         DPRINTF( "CUPSfuncs::option->num_choices %d\n", option->num_choices)
 411         // +1 is for storing the default media index
 412         sizeArray = (*env)->NewFloatArray(env, option->num_choices*6+1);
 413         if (sizeArray == NULL) {
 414             unlink(filename);
 415             j2d_ppdClose(ppd);
 416             DPRINTF("CUPSfuncs::bad alloc new float array\n", "")
 417             (*env)->ExceptionClear(env);
 418             JNU_ThrowOutOfMemoryError(env, "OutOfMemoryError");
 419             return NULL;
 420         }
 421 
 422         dims = (*env)->GetFloatArrayElements(env, sizeArray, NULL);
 423         if (dims == NULL) {
 424             unlink(filename);
 425             j2d_ppdClose(ppd);
 426             (*env)->ExceptionClear(env);
 427             JNU_ThrowOutOfMemoryError(env, "Could not create printer name");
 428             return NULL;
 429         }
 430         for (i = 0; i<option->num_choices; i++) {
 431             choice = (option->choices)+i;
 432             // get the index of the default page
 433             if (!strcmp(choice->choice, option->defchoice)) {
 434                 dims[option->num_choices*6] = (float)i;
 435             }
 436             size = j2d_ppdPageSize(ppd, choice->choice);
 437             if (size != NULL) {
 438                 // paper width and height
 439                 dims[i*6] = size->width;
 440                 dims[(i*6)+1] = size->length;
 441                 // paper printable area
 442                 dims[(i*6)+2] = size->left;
 443                 dims[(i*6)+3] = size->top;
 444                 dims[(i*6)+4] = size->right;
 445                 dims[(i*6)+5] = size->bottom;
 446             }
 447         }
 448 
 449         (*env)->ReleaseFloatArrayElements(env, sizeArray, dims, 0);
 450     }
 451 
 452     j2d_ppdClose(ppd);
 453     unlink(filename);
 454     return sizeArray;
 455 }
 456 
 457 /*
 458  * Populates the supplied ArrayList<Integer> with resolutions.
 459  * The first pair of elements will be the default resolution.
 460  * If resolution isn't supported the list will be empty.
 461  * If needed we can add a 2nd ArrayList<String> which would
 462  * be populated with the corresponding UI name.
 463  * PPD specifies the syntax for resolution as either "Ndpi" or "MxNdpi",
 464  * eg 300dpi or 600x600dpi. The former is a shorthand where xres==yres.
 465  * We will always expand to the latter as we use a single array list.
 466  * Note: getMedia() and getPageSizes() both open the ppd file
 467  * This is not going to scale forever so if we add anymore we
 468  * should look to consolidate this.
 469  */
 470 JNIEXPORT void JNICALL
 471 Java_sun_print_CUPSPrinter_getResolutions(JNIEnv *env,
 472                                           jobject printObj,
 473                                           jstring printer,
 474                                           jobject arrayList)
 475 {
 476     ppd_file_t *ppd = NULL;
 477     ppd_option_t *resolution;
 478     int defx = 0, defy = 0;
 479     int resx = 0, resy = 0;
 480     jclass intCls, cls;
 481     jmethodID intCtr, arrListAddMID;
 482     int i;
 483     const char *name = NULL;
 484     const char *filename = NULL;
 485 
 486     intCls = (*env)->FindClass(env, "java/lang/Integer");
 487     CHECK_NULL(intCls);
 488     intCtr = (*env)->GetMethodID(env, intCls, "<init>", "(I)V");
 489     CHECK_NULL(intCtr);
 490     cls = (*env)->FindClass(env, "java/util/ArrayList");
 491     CHECK_NULL(cls);
 492     arrListAddMID =
 493         (*env)->GetMethodID(env, cls, "add", "(Ljava/lang/Object;)Z");
 494     CHECK_NULL(arrListAddMID);
 495 
 496     name = (*env)->GetStringUTFChars(env, printer, NULL);
 497     if (name == NULL) {
 498         (*env)->ExceptionClear(env);
 499         JNU_ThrowOutOfMemoryError(env, "Could not create printer name");
 500         return;
 501     }
 502 
 503 
 504     // NOTE: cupsGetPPD returns a pointer to a filename of a temporary file.
 505     // unlink() must be called to remove the file after using it.
 506     filename = j2d_cupsGetPPD(name);
 507     (*env)->ReleaseStringUTFChars(env, printer, name);
 508     CHECK_NULL(filename);
 509     if ((ppd = j2d_ppdOpenFile(filename)) == NULL) {
 510         unlink(filename);
 511         DPRINTF("unable to open PPD  %s\n", filename)
 512     }
 513     resolution = j2d_ppdFindOption(ppd, "Resolution");
 514     if (resolution != NULL) {
 515         int matches = sscanf(resolution->defchoice, "%dx%ddpi", &defx, &defy);
 516         if (matches == 2) {
 517            if (defx <= 0 || defy <= 0) {
 518               defx = 0;
 519               defy = 0;
 520            }
 521         } else {
 522             matches = sscanf(resolution->defchoice, "%ddpi", &defx);
 523             if (matches == 1) {
 524                 if (defx <= 0) {
 525                    defx = 0;
 526                 } else {
 527                    defy = defx;
 528                 }
 529             }
 530         }
 531         if (defx > 0) {
 532           jobject rxObj, ryObj;
 533           rxObj = (*env)->NewObject(env, intCls, intCtr, defx);
 534           CHECK_NULL(rxObj);
 535           ryObj = (*env)->NewObject(env, intCls, intCtr, defy);
 536           CHECK_NULL(ryObj);
 537           (*env)->CallBooleanMethod(env, arrayList, arrListAddMID, rxObj);
 538           (*env)->CallBooleanMethod(env, arrayList, arrListAddMID, ryObj);
 539         }
 540 
 541         for (i = 0; i < resolution->num_choices; i++) {
 542             char *resStr = resolution->choices[i].choice;
 543             int matches = sscanf(resStr, "%dx%ddpi", &resx, &resy);
 544             if (matches == 2) {
 545                if (resx <= 0 || resy <= 0) {
 546                   resx = 0;
 547                   resy = 0;
 548                }
 549             } else {
 550                 matches = sscanf(resStr, "%ddpi", &resx);
 551                 if (matches == 1) {
 552                     if (resx <= 0) {
 553                        resx = 0;
 554                     } else {
 555                        resy = resx;
 556                     }
 557                 }
 558             }
 559             if (resx > 0 && (resx != defx || resy != defy )) {
 560               jobject rxObj, ryObj;
 561               rxObj = (*env)->NewObject(env, intCls, intCtr, resx);
 562               CHECK_NULL(rxObj);
 563               ryObj = (*env)->NewObject(env, intCls, intCtr, resy);
 564               CHECK_NULL(ryObj);
 565               (*env)->CallBooleanMethod(env, arrayList, arrListAddMID, rxObj);
 566               (*env)->CallBooleanMethod(env, arrayList, arrListAddMID, ryObj);
 567             }
 568         }
 569     }
 570 
 571     j2d_ppdClose(ppd);
 572     unlink(filename);
 573 }